watcher 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/watcher.rb ADDED
@@ -0,0 +1,596 @@
1
+ #!/usr/bin/ruby -w
2
+ # -*- Ruby -*-
3
+ # Watcher
4
+ #
5
+ # Summary:: Watcher provides advanced integrated exception handling and
6
+ # logging functionality to your Ruby programs.
7
+ # Author:: Karrick McDermott (karrick@karrick.org)
8
+ # Date:: 2008-07-04
9
+ # Copyright:: Copyright (c) 2008 by Karrick McDermott. All rights reserved.
10
+ # License:: Simplified BSD License.
11
+
12
+ ######################################################################
13
+ class Watcher
14
+ VERSION = '1.1.0'
15
+ # Tracks the number of times an Exception has invoked the :raise fail
16
+ # action.
17
+ # * Fail actions to log an error (:log), invoke a Proc (lambda {...}), and
18
+ # retry (:retry) are ignored because flow of the program is allowed to
19
+ # continue for each of those, whereas a :raise will abort processing of
20
+ # subsequent tasks until caught.
21
+ # * If a task raises an Exception, but the task is enclosed in another task
22
+ # that merely logs the Exception, then the @errors attribute is still
23
+ # incremented. This is useful for signalling when a warning condition
24
+ # occured, but the program is able to continue the processing of
25
+ # subsequent tasks.
26
+ attr_reader :errors
27
+
28
+ # Tracks the number of times an Exception has been raised, regardless of
29
+ # whether the Exception was re-thrown.
30
+ attr_reader :warnings
31
+
32
+ # Array of actions to perform when an Exception is thrown by code in the
33
+ # yield block.
34
+ # * The Watcher instance maintains a default @fail_actions Array for use
35
+ # when a given invocation does not specify a different set of actions to
36
+ # perform.
37
+ # * The default value is an Array of two actions, [:log, :raise], which
38
+ # notes the error in the logs, and re-throws the Exception.
39
+ attr_accessor :fail_actions
40
+
41
+ # A String to include inside the log entry when an Exception is thrown.
42
+ # * This may be required in some circumstances where a separate log
43
+ # monitoring program is scanning log files looking for a string such as,
44
+ # ERROR or FAILURE.
45
+ # * The default value is nil.
46
+ attr_accessor :fail_symbol
47
+
48
+ # The number of spaces to indent a child level task under its parent task.
49
+ # * A Boolean value of false or an Integer value of 0 turns off indentation.
50
+ # * Any other value will set how many characters will be used for indenting
51
+ # the logs.
52
+ # * This may only be set at the time the Watcher object is created.
53
+ # * The default value is 3.
54
+ attr_reader :indent
55
+
56
+ # A String of characters to use when joining multiple error lines into a
57
+ # single line for output to the log.
58
+ # * If nil, the Exception message is reported verbatim. If not nil, the
59
+ # value must respond to the <tt>to_s</tt> method, which is used to join
60
+ # the Exception message lines.
61
+ # * The default value is ' (LF) '.
62
+ attr_accessor :merge_newlines
63
+
64
+ # Determines whether Watcher will display the task completion log events.
65
+ # * The default value is false.
66
+ attr_accessor :show_completion
67
+
68
+ # Holds a Proc object assigned the task of formatting the system time to a
69
+ # String for display.
70
+ # * You can easily change how the times are displayed by passing a Proc
71
+ # object or a block to this attribute.
72
+ # * If you pass a block, be sure to wrap it with lambda.
73
+ #
74
+ # <tt>$watcher.time_format( lambda { Time.now.utc } )</tt>
75
+ #
76
+ # * The default value is a Proc object that returns a String representing
77
+ # local time in the standard system format.
78
+ attr_accessor :time_formatter
79
+
80
+ # Determines level of reporting that Watcher object sends to the log file.
81
+ # * Possible values are the Symbols :debug, :info, :warn, :error, and
82
+ # :fatal.
83
+ # * The default value is :info.
84
+ attr_accessor :verbosity
85
+
86
+ # Provide ability for Watcher to prioritize levels of verbosity.
87
+ # * It wasn't designed to be directly used by client code, but I suppose it
88
+ # could be modified by consumer code in this script if the situation
89
+ # warranted.
90
+ @@verbosity_levels = {:debug=>0, :info=>1, :warn=>2, :error=>3, :fatal=>4}
91
+
92
+ # Stores the Strings used by Watcher to use when writting events to the log
93
+ # files.
94
+ # * Provided as a means to customize the output, say for language
95
+ # localization purposes.
96
+ # * <b>Warning</b>, if you substitute your own Hash for @verbosity_strings
97
+ # that does not have keys for the five levels of verbosity used by
98
+ # Watcher, you may cause Watcher to start throwing exceptions with it
99
+ # attempts to coerce the NilClass into a String.
100
+ # * The default value of @verbosity_strings is a Hash that maps the
101
+ # verbosity levels to a reasonable String value.
102
+ attr_accessor :verbosity_strings
103
+
104
+ # We override the default accessibility for the :new method because we want
105
+ # to ensure there is only one Watcher object created.
106
+ private_class_method :new
107
+ @@watcher = nil
108
+
109
+ # #Watcher.create is the only way to instantiate a new Watcher object.
110
+ # * You cannot use the #new method directly as you can with regular classes.
111
+ # * Repeated calls to #Watcher.create simply return the same Watcher object.
112
+ # * When a Watcher object is created, the default class initialization
113
+ # values are usually perfectly suitable for the task at hand. This is
114
+ # more-so true when just starting on a new project, when the default is to
115
+ # send logging information to STDERR, which is usually what you want when
116
+ # developing a program using the interactive method, such as with irb.
117
+ # * It is easy to change any of the class attributes either at object
118
+ # instantiation, or while the program is being executed. During
119
+ # instantiation simply create a Hash object with a key Symbol for every
120
+ # attribute you wish to change, whose value is your desired value. For
121
+ # instance, to create a globabl variable, aptly named <tt>$watcher</tt>:
122
+ #
123
+ # <tt>
124
+ # $watcher = Watcher.create({:fail_actions=>:warn, :verbosity=>:info})
125
+ # </tt>
126
+ #
127
+ # * Each Watcher object instance stores an IO object for buffering data to
128
+ # the output log. This can either be a regular file, STDERR, STDOUT, or
129
+ # perhaps a FIFO or a pipe. If you do not want to use STDERR or a
130
+ # regular file, simply make the connection, and pass it to your Watcher
131
+ # instance.
132
+ # * The default value of @output is an IO instance of STDERR.
133
+ def Watcher.create(attributes={})
134
+ @@watcher ||= new(attributes)
135
+ end
136
+
137
+ # Used to identify a low-level task during development.
138
+ # * This is most frequently invoked without a block, in which case it simply
139
+ # appends the task start and stop messages to the log if the verbosity
140
+ # level is at :debug.
141
+ # * Use this when in lieu of a group of 'puts' statements littered through
142
+ # your source code.
143
+ # * See detailed decription for #monitor for how this method works.
144
+ def debug(task, fail_actions=@fail_actions)
145
+ monitor(task,:debug,block_given?,fail_actions) { yield }
146
+ end
147
+
148
+ # Usually the method of choice for your general logging purpose
149
+ # needs.
150
+ # * I typcially use it at every level of the stack frame to track the
151
+ # progress of the program.
152
+ # * I recommend using this for every level of program logic that you desire
153
+ # to track.
154
+ # * See detailed decription for #monitor for how this method works.
155
+ def info(task, fail_actions=@fail_actions)
156
+ monitor(task,:info,block_given?,fail_actions) { yield }
157
+ end
158
+
159
+ # Each Watcher object instance can optionally send a copy of its
160
+ # log file to a given email account.
161
+ # * You might want to call this if there were any errors while completing
162
+ # your tasks.
163
+ def send_email(email,subject)
164
+ @output.flush # flush the output buffer
165
+ system "mail -s '#{subject}' #{email} < #{@output_path}"
166
+
167
+ # reopen the output buffer for append mode
168
+ @output = File.open(@output_path, 'a')
169
+ unless @output # Redirect to STDERR if open failed.
170
+ @output = STDERR
171
+ @debug_out << "could not open [#{output}]. " + e.message + "\n" \
172
+ if WATCHER_DEBUG
173
+ end
174
+ end
175
+
176
+ ########################################
177
+ private
178
+ ########################################
179
+
180
+ # The Watcher.initialize method is called by the Watcher.new method as
181
+ # normal, however because the Watcher.new method is a private method, the
182
+ # only way to create a new Watcher object is to use the Watcher.create
183
+ # method, which in turn invokes Watcher.new, which--finally--invokes
184
+ # Watcher.initialize.
185
+ def initialize(attributes={}) #:nodoc: #:notnew:
186
+ # Watcher debugging facility
187
+ if WATCHER_DEBUG
188
+ # Open our dump file, and let Ruby environment close it when we exit.
189
+ begin
190
+ # mode 'w' truncates file if not empty.
191
+ @debug_out = File.open(WATCHER_DEBUG_OUTPUT_FILENAME,'w')
192
+ rescue Exception => e
193
+ # if we couldn't open it, just send debugging data to STDERR,
194
+ # and insert a message indicating the failure.
195
+ @debug_out = STDERR
196
+ @debug_out << "could not open [#{WATCHER_DEBUG_OUTPUT_FILENAME}]. " \
197
+ + e.message + "\n"
198
+ end
199
+ end
200
+
201
+ # Watcher debugging facility
202
+ if WATCHER_DEBUG and WATCHER_DEBUG_ATTRIBUTES
203
+ @debug_out << "=== Explicitly requested attributes: \n"
204
+ attributes.keys.each do |k|
205
+ @debug_out << "\t:#{k}=>#{attributes[k].inspect}\n"
206
+ end
207
+ end
208
+
209
+ # Here we define the default values for a newly created Watcher object.
210
+ # * If passed nil on :output, must specifically delete Hash key.
211
+ # * Merge the input attributes Hash given to Watcher.create with a
212
+ # local inline Hash below.
213
+ # * Any keys provided by the user simply cause the associated key
214
+ # values provided by the consumer code to overwrite the values
215
+ # provided in the literal Hash below. Isn't Ruby fun?
216
+ attributes.delete(:output) if attributes[:output] == nil
217
+ attributes={:fail_actions=>[:log,:raise], :fail_symbol=>nil, :indent=>3,
218
+ :merge_newlines=>' (LF) ', :output=>STDERR, :show_completion=>false,
219
+ :time_formatter=>lambda { Time.now.to_s }, :verbosity=>:info,
220
+ :verbosity_strings=>{:debug=>' DEBUG:',:info=>' INFO :',
221
+ :warn=>' WARN :',:error=>' ERROR:',
222
+ :fatal=>' FATAL:'}
223
+ }.merge(attributes)
224
+
225
+ # Watcher debugging facility
226
+ if WATCHER_DEBUG and WATCHER_DEBUG_ATTRIBUTES
227
+ @debug_out << "=== Merged attributes: \n" << "\n"
228
+ attributes.keys.each do |k|
229
+ @debug_out << "\t:#{k}=>#{attributes[k].inspect}\n"
230
+ end
231
+ end
232
+
233
+ # Here the product of the merges Hashes is used to populate the
234
+ # instantiated Watcher object attributes.
235
+ # Nothing to see here, move on... Unless you want to improve
236
+ # Watcher...
237
+ @current_indent=0
238
+ @errors=0
239
+ @warnings=0
240
+ @fail_actions=attributes[:fail_actions]
241
+ @fail_symbol=attributes[:fail_symbol]
242
+ @indent=attributes[:indent]
243
+ @merge_newlines=attributes[:merge_newlines]
244
+ @output=attributes[:output]
245
+ @show_completion=attributes[:show_completion]
246
+ @time_formatter=attributes[:time_formatter]
247
+ @verbosity=attributes[:verbosity]
248
+ @verbosity_strings=attributes[:verbosity_strings]
249
+
250
+ # Watcher debugging facility
251
+ if WATCHER_DEBUG and WATCHER_DEBUG_ATTRIBUTES
252
+ @debug_out << "=== Instance attributes: \n" << "\n"
253
+ @debug_out << " @current_indent\t\t=#{@current_indent}" << "\n"
254
+ @debug_out << " @fail_actions\t\t=#{@fail_actions.inspect}" << "\n"
255
+ @debug_out << " @fail_symbol\t\t=\"#{@fail_symbol}\"" << "\n"
256
+ @debug_out << " @indent\t\t\t=#{@indent}" << "\n"
257
+ @debug_out << " @merge_newlines\t\t=#{@merge_newlines}" << "\n"
258
+ @debug_out << " @output\t\t\t=#{@output}" << "\n"
259
+ @debug_out << " @show_completion\t\t=#{@show_completion}" << "\n"
260
+ @debug_out << " @time_formatter\t\t=#{@time_formatter}" << "\n"
261
+ @debug_out << " @verbosity\t\t\t=:#{@verbosity}" << "\n"
262
+ @debug_out << " @verbosity_strings\t\t=" \
263
+ + "#{@verbosity_strings.inspect}\n"
264
+ end
265
+
266
+ # Open the log file if not STDERR nor STDOUR
267
+ if @output != STDERR and @output != STDOUT
268
+ @output_path = File.expand_path(@output)
269
+ # mode 'w' truncates file if not empty.
270
+ @output = File.open(@output_path, 'w')
271
+ unless @output # Redirect to STDERR if open failed.
272
+ @output = STDERR
273
+ @debug_out << "could not open [#{output}]. " + e.message + "\n" \
274
+ if WATCHER_DEBUG
275
+ end
276
+ end
277
+ end # initialize
278
+
279
+ # Returns a String representing the indentation characters to use
280
+ # for the event.
281
+ # * A new event increments the indentation level.
282
+ # * A done event and an error event decrements the indentation level.
283
+ # * A nil event represents a note, with no potential child-tasks. (No
284
+ # indentation change is required.)
285
+ def get_log_indentation_string(event=nil)
286
+ if @indent.class != Fixnum # don't indent unless numberical value
287
+ case event
288
+ when nil then result = '='
289
+ when :new then result = '>'
290
+ when :done then result = '<'
291
+ when :error then result = '*'
292
+ else
293
+ result = '='
294
+ @debug_out << " =WD= Invalid event Symbol = #{event.inspect}\n" \
295
+ if WATCHER_DEBUG
296
+ end
297
+ else # we got a Fixnum, so use indented log output
298
+ result = ' ' # plan to place at least one space after the time mark
299
+ # If WATCHER_DEBUG, we want to use periods instead of spaces to indent
300
+ space = '.'
301
+ unless WATCHER_DEBUG
302
+ # create some run-time protection from bad input
303
+ # TODO: Validate data when attributes accessors invoked
304
+ @current_indent = 0 if @current_indent < 0
305
+ @indent = 3 if @indent < 3
306
+ space = ' ' # if not debugging Watcher, go ahead and use spaces
307
+ end
308
+
309
+ #--
310
+ # If @indent is >= 3, arrows are created at each indent level to show
311
+ # the parent-child relationship between tasks.
312
+ # * head refers to the head of a log event arrow, and
313
+ # * tail refers to the tail of a log event arrow.
314
+ #++
315
+ case event
316
+ when nil
317
+ # same as :new but do not increment the level at the end
318
+ @current_indent.times do
319
+ result += space * (@indent-1)
320
+ end
321
+ result += 'I' # tail
322
+ result += '=' * (@indent-2)
323
+ result += '>' # head
324
+ when :new
325
+ # increate indent after make this String
326
+ @current_indent.times do
327
+ result += space * (@indent-1)
328
+ end
329
+ result += 'N' # tail
330
+ result += '-' * (@indent-2)
331
+ result += '>' # head
332
+ @current_indent += 1
333
+ when :done
334
+ # decrease indent before make this String
335
+ @current_indent -= 1
336
+ @current_indent.times do
337
+ result += space * (@indent-1)
338
+ end
339
+ result += 'C' # head
340
+ result += '-' * (@indent-1)
341
+ when :error
342
+ # decrease indent before make this String
343
+ @current_indent -= 1
344
+ @current_indent.times do
345
+ result += space * (@indent-1)
346
+ end
347
+ result += 'E' # head
348
+ result += '-' * (@indent-2)
349
+ else
350
+ # invalid event code invoked from within Watcher.monitor
351
+ @debug_out << " =WD= Invalid event Symbol = #{event.inspect}\n" \
352
+ if WATCHER_DEBUG
353
+ end
354
+ end
355
+ result
356
+ end
357
+
358
+ # Get the time String, but protect against if plug-in Proc causes an
359
+ # Exception.
360
+ def get_protected_log_time_string
361
+ begin
362
+ @time_formatter.call
363
+ rescue
364
+ Time.now.utc.to_s + " == WARNING: Time Formatter raised Exception! == "
365
+ end
366
+ end
367
+
368
+ # When either #debug or #info is invoked, it ends up passing the
369
+ # batton to the private #monitor method.
370
+ # * #monitor first sets up some local variables, including setting up a
371
+ # variable to store the effective fail_actions for reasons decribed below,
372
+ # then optionally makes a log entry concerning the task you desire to
373
+ # execute. This log entry only takes place if the verbosity level of the
374
+ # task is equal to or exceeds the Watcher object's stored verbosity
375
+ # minimum level, @verbosity.
376
+ # * After preparations are complete, Watcher checks whether you sent the
377
+ # #debug or #info method a block to execute. If you did not send a block,
378
+ # Watcher simply returns control to your code at the point immediately
379
+ # following where you invoked Watcher.
380
+ # * If you did pass a block to execute, Watcher sets up a
381
+ # <tt>begin...rescue...end</tt> Exception handling block and invokes your
382
+ # block inside that protective environment.
383
+ # * If code invoked by that block triggers an Exception, Watcher passes
384
+ # control to the #protect method, which orchestrates a recovery path based
385
+ # on the consumer provided fail_actions Array. If the #debug or #info
386
+ # methods were not given a specific set of actions to perform if an
387
+ # Exception occurs, then the Watcher object uses its object instance
388
+ # attribute @fail_actions to provide the list of actions to complete. For
389
+ # information how this works, see the documentation for the #protect
390
+ # method.
391
+ def monitor(task,run_level,block_was_given,fail_actions) #:doc:
392
+ # Watcher debugging facility
393
+ if WATCHER_DEBUG and WATCHER_DEBUG_FAILACTIONS
394
+ @debug_out << "fail_actions = #{fail_actions.inspect}\n"
395
+ end
396
+
397
+ # assume no exception for this task
398
+ success_flag = true
399
+
400
+ # validate input upon initial invocation, not when handling Exception...
401
+ # abort task if input parameters are not clean
402
+ if not task.respond_to?(:to_s)
403
+ @output << get_protected_log_time_string
404
+ @output << "WATCHER: the 'task' must be printable!\n"
405
+ return # abort task
406
+ elsif not @@verbosity_levels.has_key?(run_level)
407
+ @output << get_protected_log_time_string + "WATCHER: The given " \
408
+ + "run_level, #{runlevel.inspect}, is not a member of " \
409
+ + "Watcher::@@verbosity_levels. Task = [#{task}]\n"
410
+ return # abort task
411
+ else
412
+ # always validate the requested fail actions by helper method
413
+ protect(task, fail_actions) # e=nil, therefore just validate
414
+ end
415
+
416
+ # Watcher debugging facility
417
+ if WATCHER_DEBUG and WATCHER_DEBUG_LEVELS
418
+ @debug_out << get_protected_log_time_string \
419
+ + " =WD= task level = :#{run_level}, " \
420
+ + "verbosity level = :#{@verbosity}\n"
421
+ end
422
+
423
+ # log the event if the task run level >= verbosity
424
+ if @@verbosity_levels[run_level] >= @@verbosity_levels[@verbosity]
425
+ output = get_protected_log_time_string
426
+ # if no new block given, then task event = nil, (don't indent)
427
+ output << get_log_indentation_string(block_was_given ? :new : nil)
428
+ output << task << "\n"
429
+ @debug_out << output if WATCHER_DEBUG and WATCHER_DEBUG_OUTPUT
430
+ @output << output
431
+ end
432
+
433
+ # okay, preparations complete, if a block was given, do it!
434
+ if block_was_given # block_given? from #debug or #info
435
+ begin
436
+ # Create a Continuation object. A :retry fail action will come back
437
+ # to this point in code. A classic "begin, rescue, retry" does not
438
+ # work, because the retry would occur from an invoked method, protect.
439
+ callcc{|@continuation|}
440
+ yield
441
+ # Log the event if the task run level is greater or equal to the
442
+ # verbosity
443
+ # * The task complete log entry only takes place after your block
444
+ # returns.
445
+ # * If you did not send a task block to complete, Watcher will
446
+ # conditionally log the event based on the verbosity level, then
447
+ # exit back to your code just after you invoked Watcher.
448
+ # * PROGRAMMER NOTE: I moved the code from the ensure block to just
449
+ # after the yield code in the begin block.
450
+ # * The reason for this is that there are really two logical ways
451
+ # to complete a task: (1) successfully, and (2) unsuccessfully.
452
+ # 1. This code represents the path by successful code, while the
453
+ # rescue block represents the patch by unsuccessful code.
454
+ # 2. Because the ensure block is always taking place, a failed block
455
+ # of consumer code would log the error, then enter the ensure
456
+ # block and log the task complete. That gives the wrong
457
+ # impression.
458
+ if @@verbosity_levels[run_level] >= @@verbosity_levels[@verbosity]
459
+ # Only display if user wants logging of compeltion events
460
+ # * Although we don't show the line when @show_completion is false,
461
+ # we must still call #get_log_indentation_string(:done) to adjust
462
+ # the @current_indent attribute properly.
463
+ output << get_protected_log_time_string
464
+ output << get_log_indentation_string(:done) << task << "\n"
465
+ if @show_completion
466
+ @debug_out << output if WATCHER_DEBUG and WATCHER_DEBUG_OUTPUT
467
+ @output << output
468
+ end
469
+ end
470
+ rescue
471
+ # catch any Exceptions thrown by the executed block
472
+ success_flag = protect(task, fail_actions, $!)
473
+ end
474
+ end
475
+
476
+ # return whether no exceptions occured
477
+ success_flag
478
+ end
479
+
480
+ # Iterate through fail_actions parameter for a task, and for each action,
481
+ # either perform or simply ensure it's a valid action.
482
+ # * #protect is called by #monitor when your code invokes #debug or #info in
483
+ # order to validate the fail_actions parameter.
484
+ # * #protect is also called by #monitor when the associated code block
485
+ # triggers an Exception. It receives an Array of actions to perform,
486
+ # <tt>fail_actions</tt>, and executes each one in turn.
487
+ # * Perhaps the most complex--however reasonable--example would be if you
488
+ # wanted to log the event, then invoke some snippet of code to attempt to
489
+ # resolve the problem, then retry the block. This can be done by
490
+ # specifying the following fail actions. <b>(Be careful, however, as this
491
+ # can easily lead to an infinite loop if the recovery Proc does not
492
+ # resolve the cause of the original Exception!)</b>
493
+ # * This is meant to provide you a bit of flexibility on how to handle
494
+ # conditions, however a complex Array of fail_actions may defeat your goal
495
+ # of code simplification.
496
+ def protect(task, fail_actions, e=nil) #:doc:
497
+ # * If e is nil, then return true if valid group, false or raise
498
+ # exception if invalid fail action found.
499
+ # * If e is an Exception, perform required actions.
500
+
501
+ # only increase warning if also performing actions
502
+ if e != nil
503
+ if e.kind_of?(Exception)
504
+ @warnings += 1
505
+ else
506
+ err_msg = "invalid exception instance (Watcher.monitor error)"
507
+ raise ArgumentError.new(err_msg)
508
+ end
509
+ @debug_out << "e = #{e.inspect}\n" if WATCHER_DEBUG
510
+ end
511
+
512
+ if fail_actions.class == Symbol or fail_actions.class == Proc
513
+ # NOTE: we pass the fail_actions (plural) parameter
514
+ protect_action(task, fail_actions, e)
515
+ elsif fail_actions.class == Array
516
+ fail_actions.each do |fail_action|
517
+ # NOTE: we pass the fail_action (singular) loop iteration variable
518
+ protect_action(task, fail_action, e)
519
+ end
520
+ else
521
+ err_msg = "unsupported fail action for task = [#{task}] " \
522
+ + "(must be Symbol or Array of Symbols and Procs)"
523
+ raise ArgumentError.new(err_msg)
524
+ end
525
+ end
526
+
527
+ # Either perform action, or simply validate it's valid
528
+ def protect_action(task, fail_action, e)
529
+ # * If e is nil, then validate the action against the possible
530
+ # actions. Raise ArgumentError Exception if invalid action found.
531
+ # * If e is a kind of Exception, perform required action.
532
+
533
+ # only time-stamp if also performing actions
534
+ if e != nil
535
+ # create the time stamp and indentation portion of the log entry
536
+ log_base = get_protected_log_time_string \
537
+ + get_log_indentation_string(:error)
538
+ @debug_out << "e = #{e.inspect}\n" if WATCHER_DEBUG
539
+ end
540
+
541
+ if fail_action.class == Proc
542
+ if e != nil
543
+ # return result from user-specified code
544
+ return fail_action.call(e) # send the Exception instance
545
+ end
546
+ elsif fail_action.class == Symbol
547
+ case fail_action
548
+ when :log
549
+ if e != nil
550
+ output = log_base + '*' + task + '*' + ' ==> '
551
+ output << "#{@fail_symbol} ==> " if @fail_symbol
552
+ if @merge_newlines != false
553
+ output << e.message.split(/\n/).join(@merge_newlines)
554
+ else # no merge, but multiple lines
555
+ output << e.message
556
+ end
557
+ @output << output << "\n"
558
+ end
559
+ when :raise
560
+ if e != nil
561
+ @errors += 1
562
+ raise
563
+ end
564
+ when :retry # FIXME: this is not well tested
565
+ @continuation.call if e != nil
566
+ else
567
+ err_msg = "unsupported fail action Symbol for task = [#{task}] " \
568
+ + "(must be one of [:log, :raise, :retry])"
569
+ raise ArgumentError.new(err_msg)
570
+ end
571
+ else # only Symbols and Procs can be designated fail actions
572
+ raise ArgumentError.new("invalid fail action (Watcher.protect error)")
573
+ end
574
+ end
575
+
576
+ # Set to true when Watcher should execute itself in debugging mode.
577
+ WATCHER_DEBUG=false
578
+
579
+ # Name of file that Watcher creates for its debugging output.
580
+ WATCHER_DEBUG_OUTPUT_FILENAME='watcher-debug.log'
581
+
582
+ # Set to true when debugging Watcher instance attribute settings.
583
+ WATCHER_DEBUG_ATTRIBUTES=false
584
+
585
+ # Set to true when debugging Watcher handling of fail actions.
586
+ WATCHER_DEBUG_FAILACTIONS=false
587
+
588
+ # Set to true when debugging Watcher verbosity levels.
589
+ WATCHER_DEBUG_LEVELS=false
590
+
591
+ # Set to true when debugging Watcher.monitor.
592
+ WATCHER_DEBUG_MONITOR=false
593
+
594
+ # Set to true when debugging Watcher's log strings.
595
+ WATCHER_DEBUG_OUTPUT=false
596
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/watcher.rb'}"
9
+ puts "Loading watcher gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)