whistle 0.1
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.
- data/History.txt +3 -0
- data/README.txt +38 -0
- data/bin/whistle +90 -0
- data/lib/config.rb +19 -0
- data/lib/phash.rb +16 -0
- data/lib/relay.rb +24 -0
- data/lib/resource.rb +113 -0
- data/lib/ssl_patch.rb +15 -0
- data/lib/switchbox.rb +54 -0
- data/lib/time_ext.rb +30 -0
- data/lib/version.rb +3 -0
- data/sample/config.yml +12 -0
- data/vendor/rscm-0.5.1-patched-stripped/README +218 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm.rb +14 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/abstract_log_parser.rb +35 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/base.rb +289 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/command_line.rb +146 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/difftool.rb +44 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/line_editor.rb +46 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/mockit.rb +157 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/parser.rb +39 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/path_converter.rb +60 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/platform.rb +26 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision.rb +103 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision_file.rb +85 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision_poller.rb +93 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revisions.rb +79 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/clearcase.rb +182 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/cvs.rb +374 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/cvs_log_parser.rb +154 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/darcs.rb +120 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/darcs_log_parser.rb +65 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/monotone.rb +338 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/monotone_log_parser.rb +109 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/mooky.rb +6 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/perforce.rb +216 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/star_team.rb +104 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/subversion.rb +397 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/subversion_log_parser.rb +165 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/tempdir.rb +17 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/time_ext.rb +11 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/version.rb +13 -0
- data/vendor/ruby-feedparser-0.5-stripped/README +14 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser.rb +28 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/feedparser.rb +300 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/filesizes.rb +12 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/html-output.rb +126 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/html2text-parser.rb +409 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/rexml_patch.rb +28 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/sgml-parser.rb +332 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/text-output.rb +83 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/textconverters.rb +120 -0
- metadata +132 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rscm/revision_poller'
|
2
|
+
require 'rscm/path_converter'
|
3
|
+
require 'rscm/difftool'
|
4
|
+
require 'rscm/platform'
|
5
|
+
require 'rscm/command_line'
|
6
|
+
require 'rscm/base'
|
7
|
+
require 'rscm/revision'
|
8
|
+
require 'rscm/revision_file'
|
9
|
+
require 'rscm/time_ext'
|
10
|
+
# Load all sources under scm
|
11
|
+
Dir[File.dirname(__FILE__) + "/rscm/scm/*.rb"].each do |src|
|
12
|
+
require src
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module RSCM
|
2
|
+
|
3
|
+
# NOTE: It is recommended to use the Parser class in parser.rb
|
4
|
+
# as a basis for new SCM parsers
|
5
|
+
#
|
6
|
+
# Some utilities for log-parsers
|
7
|
+
# TODO: make this a module and remove the attr_reader
|
8
|
+
class AbstractLogParser
|
9
|
+
|
10
|
+
def initialize(io)
|
11
|
+
@io = io
|
12
|
+
end
|
13
|
+
|
14
|
+
def read_until_matching_line(regexp)
|
15
|
+
return nil if @io.eof?
|
16
|
+
result = ""
|
17
|
+
@io.each_line do |line|
|
18
|
+
line.gsub!(/\r\n$/, "\n")
|
19
|
+
break if line =~ regexp
|
20
|
+
result << line
|
21
|
+
end
|
22
|
+
if result.strip == ""
|
23
|
+
read_until_matching_line(regexp)
|
24
|
+
else
|
25
|
+
result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def convert_all_slashes_to_forward_slashes(file)
|
30
|
+
file.gsub(/\\/, "/")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rscm/revision'
|
3
|
+
require 'rscm/path_converter'
|
4
|
+
|
5
|
+
module RSCM
|
6
|
+
# This class defines the RSCM API, which offers access to an SCM working copy
|
7
|
+
# as well as a 'central' repository.
|
8
|
+
#
|
9
|
+
# Concrete subclasses of this class (concrete adapters) implement the integration
|
10
|
+
# with the respective SCMs.
|
11
|
+
#
|
12
|
+
# Most of the methods take an optional +options+ Hash (named parameters), allowing
|
13
|
+
# the following options:
|
14
|
+
#
|
15
|
+
# * <tt>:stdout</tt>: Path to file name where stdout of SCM operations are written.
|
16
|
+
# * <tt>:stdout</tt>: Path to file name where stderr of SCM operations are written.
|
17
|
+
#
|
18
|
+
# In stead of specifying the +options+ parameters for every API method, it's possible
|
19
|
+
# to assign default options via the +default_options+ attribute.
|
20
|
+
#
|
21
|
+
# Some of the methods in this API use +from_identifier+ and +to_identifier+.
|
22
|
+
# These identifiers can be either a UTC Time (according to the SCM's clock)
|
23
|
+
# or a String or Integer representing a label/revision
|
24
|
+
# (according to the SCM's native label/revision scheme).
|
25
|
+
#
|
26
|
+
# If +from_identifier+ or +to_identifier+ are +nil+ they should respectively default to
|
27
|
+
# Time.epoch or Time.infinite.
|
28
|
+
#
|
29
|
+
class Base
|
30
|
+
include RevisionPoller
|
31
|
+
|
32
|
+
attr_writer :default_options
|
33
|
+
attr_writer :store_revisions_command
|
34
|
+
|
35
|
+
def default_options
|
36
|
+
@default_options ||= {}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns true if the underlying SCM tool is available on this system.
|
40
|
+
def available?
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
|
44
|
+
# Transforms +raw_identifier+ into the native rype used for revisions.
|
45
|
+
def to_identifier(raw_identifier)
|
46
|
+
raw_identifier.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
# Sets the checkout dir (working copy). Should be set prior to most other method
|
50
|
+
# invocations (depending on the implementation).
|
51
|
+
def checkout_dir=(dir)
|
52
|
+
@checkout_dir = PathConverter.filepath_to_nativepath(dir, false)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Gets the working copy directory.
|
56
|
+
def checkout_dir
|
57
|
+
@checkout_dir
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_yaml_properties #:nodoc:
|
61
|
+
props = instance_variables
|
62
|
+
props.delete("@checkout_dir")
|
63
|
+
props.delete("@default_options")
|
64
|
+
props.sort!
|
65
|
+
end
|
66
|
+
|
67
|
+
# Destroys the working copy
|
68
|
+
def destroy_working_copy(options={})
|
69
|
+
FileUtils.rm_rf(checkout_dir) unless checkout_dir.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
# Whether or not the SCM represented by this instance exists.
|
73
|
+
def central_exists?
|
74
|
+
# The default implementation assumes yes - override if it can be
|
75
|
+
# determined programmatically.
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
# Whether or not this SCM is transactional (atomic).
|
80
|
+
def transactional?
|
81
|
+
false
|
82
|
+
end
|
83
|
+
alias :atomic? :transactional?
|
84
|
+
|
85
|
+
# Creates a new 'central' repository. This is intended only for creation of 'central'
|
86
|
+
# repositories (not for working copies). You shouldn't have to call this method if a central repository
|
87
|
+
# already exists. This method is used primarily for testing of RSCM, but can also
|
88
|
+
# be used if you *really* want to use RSCM to create a central repository.
|
89
|
+
#
|
90
|
+
# This method should throw an exception if the repository cannot be created (for
|
91
|
+
# example if the repository is 'remote' or if it already exists).
|
92
|
+
#
|
93
|
+
def create_central(options={})
|
94
|
+
raise NotImplementedError
|
95
|
+
end
|
96
|
+
|
97
|
+
# Destroys the central repository. Shuts down any server processes and deletes the repository.
|
98
|
+
# WARNING: calling this may result in loss of data. Only call this if you really want to wipe
|
99
|
+
# it out for good!
|
100
|
+
def destroy_central
|
101
|
+
raise NotImplementedError
|
102
|
+
end
|
103
|
+
|
104
|
+
# Whether a repository can be created.
|
105
|
+
def can_create_central?
|
106
|
+
false
|
107
|
+
end
|
108
|
+
|
109
|
+
# Adds +relative_filename+ to the working copy.
|
110
|
+
def add(relative_filename, options={})
|
111
|
+
raise NotImplementedError
|
112
|
+
end
|
113
|
+
|
114
|
+
# Schedules a move of +relative_src+ to +relative_dest+
|
115
|
+
# Should not take effect in the central repository until
|
116
|
+
# +commit+ is invoked.
|
117
|
+
def move(relative_src, relative_dest, options={})
|
118
|
+
raise NotImplementedError
|
119
|
+
end
|
120
|
+
|
121
|
+
# Recursively imports files from <tt>:dir</tt> into the central scm,
|
122
|
+
# using commit message <tt>:message</tt>
|
123
|
+
def import_central(options)
|
124
|
+
raise NotImplementedError
|
125
|
+
end
|
126
|
+
|
127
|
+
# Open a file for edit - required by scms that check out files in read-only mode e.g. perforce
|
128
|
+
def edit(file, options={})
|
129
|
+
end
|
130
|
+
|
131
|
+
# Commit (check in) modified files.
|
132
|
+
def commit(message, options={})
|
133
|
+
raise NotImplementedError
|
134
|
+
end
|
135
|
+
|
136
|
+
# Checks out or updates contents from a central SCM to +checkout_dir+ - a local working copy.
|
137
|
+
# If this is a distributed SCM, this method should create a 'working copy' repository
|
138
|
+
# if one doesn't already exist. Then the contents of the central SCM should be pulled into
|
139
|
+
# the working copy.
|
140
|
+
#
|
141
|
+
# The +to_identifier+ parameter may be optionally specified to obtain files up to a
|
142
|
+
# particular time or label. +to_identifier+ should either be a Time (in UTC - according to
|
143
|
+
# the clock on the SCM machine) or a String - reprsenting a label or revision.
|
144
|
+
#
|
145
|
+
# This method will yield the relative file name of each checked out file, and also return
|
146
|
+
# them in an array. Only files, not directories, should be yielded/returned.
|
147
|
+
#
|
148
|
+
# This method should be overridden for SCMs that are able to yield checkouts as they happen.
|
149
|
+
# For some SCMs this is not possible, or at least very hard. In that case, just override
|
150
|
+
# the checkout_silent method instead of this method (should be protected).
|
151
|
+
#
|
152
|
+
def checkout(to_identifier=Time.infinity, options={}) # :yield: file
|
153
|
+
to_identifier = Time.infinity if to_identifier.nil?
|
154
|
+
|
155
|
+
before = checked_out_files
|
156
|
+
# We expect subclasses to implement this as a protected method (unless this whole method is overridden).
|
157
|
+
checkout_silent(to_identifier, options)
|
158
|
+
after = checked_out_files
|
159
|
+
|
160
|
+
(after - before).sort!
|
161
|
+
end
|
162
|
+
|
163
|
+
def checked_out_files
|
164
|
+
raise "checkout_dir not set" if @checkout_dir.nil?
|
165
|
+
|
166
|
+
files = Dir["#{@checkout_dir}/**/*"]
|
167
|
+
files.delete_if{|file| File.directory?(file)}
|
168
|
+
ignore_paths.each do |regex|
|
169
|
+
files.delete_if{|file| file =~ regex}
|
170
|
+
end
|
171
|
+
dir = File.expand_path(@checkout_dir)
|
172
|
+
files.collect{|file| File.expand_path(file)[dir.length+1..-1]}
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns a Revisions object for the interval specified by +from_identifier+ (exclusive, i.e. after)
|
176
|
+
# and optionally +:to_identifier+ (exclusive too). If +relative_path+ is specified, the result will only contain
|
177
|
+
# revisions pertaining to that path.
|
178
|
+
#
|
179
|
+
# For example, revisions(223, 229) should return revisions 224..228
|
180
|
+
def revisions(from_identifier, options={})
|
181
|
+
raise NotImplementedError
|
182
|
+
end
|
183
|
+
|
184
|
+
# Opens a readonly IO to a file at +path+
|
185
|
+
def open(path, native_revision_identifier, options={}, &block) #:yield: io
|
186
|
+
raise NotImplementedError
|
187
|
+
end
|
188
|
+
|
189
|
+
# Whether the working copy is in synch with the central
|
190
|
+
# repository's revision/time identified by +identifier+.
|
191
|
+
# If +identifier+ is nil, 'HEAD' of repository should be assumed.
|
192
|
+
#
|
193
|
+
def uptodate?(identifier)
|
194
|
+
raise NotImplementedError
|
195
|
+
end
|
196
|
+
|
197
|
+
# Whether the project is checked out from the central repository or not.
|
198
|
+
# Subclasses should override this to check for SCM-specific administrative
|
199
|
+
# files if appliccable
|
200
|
+
def checked_out?
|
201
|
+
File.exists?(@checkout_dir)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Whether triggers are supported by this SCM. A trigger is a command that can be executed
|
205
|
+
# upon a completed commit to the SCM.
|
206
|
+
def supports_trigger?
|
207
|
+
# The default implementation assumes no - override if it can be
|
208
|
+
# determined programmatically.
|
209
|
+
false
|
210
|
+
end
|
211
|
+
alias :can_install_trigger? :supports_trigger?
|
212
|
+
|
213
|
+
# Descriptive name of the trigger mechanism
|
214
|
+
def trigger_mechanism
|
215
|
+
raise NotImplementedError
|
216
|
+
end
|
217
|
+
|
218
|
+
# Installs +trigger_command+ in the SCM.
|
219
|
+
# The +install_dir+ parameter should be an empty local
|
220
|
+
# directory that the SCM can use for temporary files
|
221
|
+
# if necessary (CVS needs this to check out its administrative files).
|
222
|
+
# Most implementations will ignore this parameter.
|
223
|
+
#
|
224
|
+
def install_trigger(trigger_command, install_dir)
|
225
|
+
raise NotImplementedError
|
226
|
+
end
|
227
|
+
|
228
|
+
# Uninstalls +trigger_command+ from the SCM.
|
229
|
+
#
|
230
|
+
def uninstall_trigger(trigger_command, install_dir)
|
231
|
+
raise NotImplementedError
|
232
|
+
end
|
233
|
+
|
234
|
+
# Whether the command denoted by +trigger_command+ is installed in the SCM.
|
235
|
+
#
|
236
|
+
def trigger_installed?(trigger_command, install_dir)
|
237
|
+
raise NotImplementedError
|
238
|
+
end
|
239
|
+
|
240
|
+
# The command line to run in order to check out a fresh working copy.
|
241
|
+
#
|
242
|
+
def checkout_commandline(to_identifier=Time.infinity)
|
243
|
+
raise NotImplementedError
|
244
|
+
end
|
245
|
+
|
246
|
+
# The command line to run in order to update a working copy.
|
247
|
+
#
|
248
|
+
def update_commandline(to_identifier=Time.infinity)
|
249
|
+
raise NotImplementedError
|
250
|
+
end
|
251
|
+
|
252
|
+
# Yields an IO containing the unified diff of the change.
|
253
|
+
# Also see RevisionFile#diff
|
254
|
+
def diff(path, from, to, options={}, &block)
|
255
|
+
raise NotImplementedError
|
256
|
+
end
|
257
|
+
|
258
|
+
def ==(other_scm)
|
259
|
+
return false if self.class != other_scm.class
|
260
|
+
self.instance_variables.each do |var|
|
261
|
+
return false if self.instance_eval(var) != other_scm.instance_eval(var)
|
262
|
+
end
|
263
|
+
true
|
264
|
+
end
|
265
|
+
|
266
|
+
# Whether or not to store the revision command in the Revisions instance returned by <tt>revisions</tt>
|
267
|
+
def store_revisions_command?; @store_revisions_command.nil? ? true : @store_revisions_command; end
|
268
|
+
|
269
|
+
protected
|
270
|
+
|
271
|
+
# Directory where commands must be run
|
272
|
+
def cmd_dir
|
273
|
+
nil
|
274
|
+
end
|
275
|
+
|
276
|
+
# Wrapper for CommandLine.execute that provides default values for
|
277
|
+
# dir plus any options set in default_options (typically stdout and stderr).
|
278
|
+
def execute(cmd, options={}, &proc)
|
279
|
+
options = {:dir => cmd_dir}.merge(default_options).merge(options)
|
280
|
+
begin
|
281
|
+
CommandLine.execute(cmd, options, &proc)
|
282
|
+
rescue CommandLine::OptionError => e
|
283
|
+
e.message += "\nEither specify default_options on the scm object, or pass the required options to the method"
|
284
|
+
raise e
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'rscm/platform'
|
2
|
+
|
3
|
+
module RSCM
|
4
|
+
module CommandLine
|
5
|
+
QUOTE_REPLACEMENT = (Platform.family == "mswin32") ? "\"" : "\\\""
|
6
|
+
LESS_THAN_REPLACEMENT = (Platform.family == "mswin32") ? "<" : "\\<"
|
7
|
+
class OptionError < StandardError; end
|
8
|
+
class ExecutionError < StandardError
|
9
|
+
attr_reader :cmd, :dir, :exitstatus, :stderr
|
10
|
+
def initialize(cmd, full_cmd, dir, exitstatus, stderr)
|
11
|
+
@cmd, @full_cmd, @dir, @exitstatus, @stderr = cmd, full_cmd, dir, exitstatus, stderr
|
12
|
+
end
|
13
|
+
def to_s
|
14
|
+
"\ndir : #{@dir}\n" +
|
15
|
+
"command : #{@cmd}\n" +
|
16
|
+
"executed command : #{@full_cmd}\n" +
|
17
|
+
"exitstatus: #{@exitstatus}\n" +
|
18
|
+
"STDERR TAIL START\n#{@stderr}\nSTDERR TAIL END\n"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Executes +cmd+.
|
23
|
+
# If the +:stdout+ and +:stderr+ options are specified, a line consisting
|
24
|
+
# of a prompt (including +cmd+) will be appended to the respective output streams will be appended
|
25
|
+
# to those files, followed by the output itself. Example:
|
26
|
+
#
|
27
|
+
# CommandLine.execute("echo hello world", {:stdout => "stdout.log", :stderr => "stderr.log"})
|
28
|
+
#
|
29
|
+
# will result in the following being written to stdout.log:
|
30
|
+
#
|
31
|
+
# /Users/aslakhellesoy/scm/buildpatterns/repos/damagecontrol/trunk aslakhellesoy$ echo hello world
|
32
|
+
# hello world
|
33
|
+
#
|
34
|
+
# -and to stderr.log:
|
35
|
+
# /Users/aslakhellesoy/scm/buildpatterns/repos/damagecontrol/trunk aslakhellesoy$ echo hello world
|
36
|
+
#
|
37
|
+
# If a block is passed, the stdout io will be yielded to it (as with IO.popen). In this case the output
|
38
|
+
# will not be written to the stdout file (even if it's specified):
|
39
|
+
#
|
40
|
+
# /Users/aslakhellesoy/scm/buildpatterns/repos/damagecontrol/trunk aslakhellesoy$ echo hello world
|
41
|
+
# [output captured and therefore not logged]
|
42
|
+
#
|
43
|
+
# If the exitstatus of the command is different from the value specified by the +:exitstatus+ option
|
44
|
+
# (which defaults to 0) then an ExecutionError is raised, its message containing the last 400 bytes of stderr
|
45
|
+
# (provided +:stderr+ was specified)
|
46
|
+
#
|
47
|
+
# You can also specify the +:dir+ option, which will cause the command to be executed in that directory
|
48
|
+
# (default is current directory).
|
49
|
+
#
|
50
|
+
# You can also specify a hash of environment variables in +:env+, which will add additional environment variables
|
51
|
+
# to the default environment.
|
52
|
+
#
|
53
|
+
# Finally, you can specify several commands within one by separating them with '&&' (as you would in a shell).
|
54
|
+
# This will result in several lines to be appended to the log (as if you had executed the commands separately).
|
55
|
+
#
|
56
|
+
# See the unit test for more examples.
|
57
|
+
def execute(cmd, options={}, &proc)
|
58
|
+
raise "Can't have newline in cmd" if cmd =~ /\n/
|
59
|
+
options = {
|
60
|
+
:dir => Dir.pwd,
|
61
|
+
:env => {},
|
62
|
+
:mode => 'r',
|
63
|
+
:exitstatus => 0
|
64
|
+
}.merge(options)
|
65
|
+
|
66
|
+
options[:stdout] = File.expand_path(options[:stdout]) if options[:stdout]
|
67
|
+
options[:stderr] = File.expand_path(options[:stderr]) if options[:stderr]
|
68
|
+
|
69
|
+
if options[:dir].nil?
|
70
|
+
e(cmd, options, &proc)
|
71
|
+
else
|
72
|
+
Dir.chdir(options[:dir]) do
|
73
|
+
e(cmd, options, &proc)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
module_function :execute
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def full_cmd(cmd, options, &proc)
|
82
|
+
commands = cmd.split("&&").collect{|c| c.strip}
|
83
|
+
stdout_opt = options[:stdout] ? ">> #{options[:stdout]}" : ""
|
84
|
+
stderr_opt = options[:stderr] ? "2>> #{options[:stderr]}" : ""
|
85
|
+
capture_info_command = (block_given? && options[:stdout])? "echo [output captured and therefore not logged] >> #{options[:stdout]} && " : ""
|
86
|
+
|
87
|
+
full_cmd = commands.collect do |c|
|
88
|
+
escaped_command = c.gsub(/"/, QUOTE_REPLACEMENT).gsub(/</, LESS_THAN_REPLACEMENT)
|
89
|
+
stdout_prompt_command = options[:stdout] ? "echo #{RSCM::Platform.prompt} #{escaped_command} >> #{options[:stdout]} && " : ""
|
90
|
+
stderr_prompt_command = options[:stderr] ? "echo #{RSCM::Platform.prompt} #{escaped_command} >> #{options[:stderr]} && " : ""
|
91
|
+
redirected_command = block_given? ? "#{c} #{stderr_opt}" : "#{c} #{stdout_opt} #{stderr_opt}"
|
92
|
+
|
93
|
+
stdout_prompt_command + capture_info_command + stderr_prompt_command + redirected_command
|
94
|
+
end.join(" && ")
|
95
|
+
end
|
96
|
+
module_function :full_cmd
|
97
|
+
|
98
|
+
def verify_exit_code(cmd, full_cmd, options)
|
99
|
+
if($?.exitstatus != options[:exitstatus])
|
100
|
+
error_message = "#{options[:stderr]} doesn't exist"
|
101
|
+
if options[:stderr] && File.exist?(options[:stderr])
|
102
|
+
File.open(options[:stderr]) do |errio|
|
103
|
+
begin
|
104
|
+
errio.seek(-1200, IO::SEEK_END)
|
105
|
+
rescue Errno::EINVAL
|
106
|
+
# ignore - it just means we didn't have 400 bytes.
|
107
|
+
end
|
108
|
+
error_message = errio.read
|
109
|
+
end
|
110
|
+
end
|
111
|
+
raise ExecutionError.new(cmd, full_cmd, options[:dir] || Dir.pwd, $?.exitstatus, error_message)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
module_function :verify_exit_code
|
115
|
+
|
116
|
+
def e(cmd, options, &proc)
|
117
|
+
full_cmd = full_cmd(cmd, options, &proc)
|
118
|
+
|
119
|
+
options[:env].each{|k,v| ENV[k]=v}
|
120
|
+
begin
|
121
|
+
STDOUT.puts "#{RSCM::Platform.prompt} #{cmd}" if options[:stdout].nil?
|
122
|
+
IO.popen(full_cmd, options[:mode]) do |io|
|
123
|
+
if(block_given?)
|
124
|
+
proc.call(io)
|
125
|
+
else
|
126
|
+
io.each_line do |line|
|
127
|
+
STDOUT.puts line if options[:stdout].nil?
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
rescue Errno::ENOENT => e
|
132
|
+
unless options[:stderr].nil?
|
133
|
+
File.open(options[:stderr], "a") {|io| io.write(e.message)}
|
134
|
+
else
|
135
|
+
STDERR.puts e.message
|
136
|
+
STDERR.puts e.backtrace.join("\n")
|
137
|
+
end
|
138
|
+
raise ExecutionError.new(cmd, full_cmd, options[:dir] || Dir.pwd, nil, e.message)
|
139
|
+
ensure
|
140
|
+
verify_exit_code(cmd, full_cmd, options)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
module_function :e
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|