watcher 1.1.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.
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)