File Coverage

blib/lib/ProgressMonitor.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             package ProgressMonitor;
2            
3 10     10   47394 use warnings;
  10         21  
  10         318  
4 10     10   54 use strict;
  10         17  
  10         292  
5            
6 10     10   277 use 5.008;
  10         42  
  10         1024  
7            
8             our $VERSION = '0.33';
9            
10             # Here follows the closest we come to describing an interface.
11             #
12             use classes 0.943
13 10         194 new => 'ABSTRACT',
14             clone => 'classes::clone',
15             methods => {
16             begin => 'ABSTRACT',
17             end => 'ABSTRACT',
18             isCanceled => 'ABSTRACT',
19             prepare => 'ABSTRACT',
20             setCanceled => 'ABSTRACT',
21             setMessage => 'ABSTRACT',
22             setErrorMessage => 'ABSTRACT',
23             tick => 'ABSTRACT',
24             subMonitor => 'ABSTRACT',
25 10     10   2974 };
  10         39908  
26            
27             ############################
28            
29             =head1 NAME
30            
31             ProgressMonitor - a flexible and configurable framework for providing feedback on how a long-running task is proceeding.
32            
33             =head1 VERSION
34            
35             Version 0.31
36            
37             =head1 SYNOPSIS
38            
39             use strict;
40             use warnings;
41            
42             use Time::HiRes qw(usleep);
43            
44             use ProgressMonitor::Stringify::ToStream;
45             use ProgressMonitor::Stringify::Fields::Bar;
46             use ProgressMonitor::Stringify::Fields::Fixed;
47             use ProgressMonitor::Stringify::Fields::Percentage;
48            
49             sub someTask
50             {
51             my $monitor = shift;
52            
53             $monitor->prepare();
54             $monitor->begin(100);
55             for (1 .. 40)
56             {
57             usleep(100_000);
58             $monitor->tick(1);
59             }
60            
61             anotherTask($monitor->subMonitor({parentTicks => 20}));
62            
63             for (1 .. 40)
64             {
65             usleep(100_000);
66             $monitor->tick(1);
67             }
68            
69             $monitor->end();
70             }
71            
72             sub anotherTask
73             {
74             my $monitor = shift;
75            
76             $monitor->prepare();
77             $monitor->begin(3000);
78            
79             for (1 .. 3000)
80             {
81             usleep(1_000);
82             $monitor->tick(1);
83             }
84            
85             $monitor->end();
86             }
87            
88             someTask(
89             ProgressMonitor::Stringify::ToStream->new(
90             {
91             fields =>
92             [
93             ProgressMonitor::Stringify::Fields::Bar->new,
94             ProgressMonitor::Stringify::Fields::Fixed->new,
95             ProgressMonitor::Stringify::Fields::Percentage->new,
96             ]
97             }
98             )
99             );
100            
101             =head1 DESCRIPTION
102            
103             The above synopsis shows it in a nutshell - cut and paste and try it. Or, peruse
104             the examples in the examples/ directory.
105            
106             =head2 BACKGROUND
107            
108             This is one more implementation of the idea of making code report progress in
109             what it's doing, and, typically, use this reporting to give feedback to a user
110             on how far we've come and/or how much there is left to do. There are other
111             Perl modules for this, but this is bigger and better :-).
112            
113             The rationale for this module was twofold: first, I needed a reasonable-sized
114             excuse to try out Rob Muhlestein's (RMUHLE) 'classes' pragma as preparation for
115             a later, bigger project. Second, I like IProgressMonitor, a Java interface in
116             the Eclipse project (more information in L below) that I've dealt
117             with and thought the basic ideas around that could be put to good use in Perl
118             code also. There are some differences from IProgressMonitor though, if you end
119             up comparing them.
120            
121             =head2 CONCEPT
122            
123             An overall principle of a feedback mechanism is that the code doing progress really
124             has no idea as to how (if at all) any of the progress it reports actually result in
125             any feedback, or, more importantly, what form such feedback takes. Thus, this package
126             only provides an abstract interface for progressee's to call into, and this has to be
127             done in a clearly defined manner so as to ensure that any feedback will indicate
128             the right things at the right time. The object of all this is to keep the user informed: 'cool
129             your heels - maybe it's taking a long time but I *am* working!'.
130            
131             Thus, any code that has no UI of its own should,
132             in the best of worlds, accept an instance of the ProgressMonitor interface, and
133             report its progress through that. Ideally, an entire framework should at all appropriate
134             places be able to make use of a monitor in order to allow the most granular feedback
135             possible.
136            
137             The other side of the coin is the code that does have an UI and thus knows
138             how feedback should be shown. This code should instantiate a monitor of the correct
139             type and pass it in to the method that reports progress.
140            
141             There is a middle ground however: a progresse may have need to use lower level functions
142             and they might also be able to use a monitor. In such a case, the higher level progressee
143             should instantiate a SubTask monitor. This special monitor type will pass on information
144             to the parent monitor and cause feedback to be correctly scaled. It is important
145             to never pass on the monitor you have been given to someone else!
146            
147             =head2 The ProgressMonitor 'contract'
148            
149             So, being a progressee, you get a monitor instance. What do you do?
150            
151             =over 2
152            
153             =item PREPARING YOURSELF
154            
155             The first call you should do as soon as possible is to call the 'prepare' method.
156             This tells the monitor that you are in prepare mode, and this means that you now spend
157             your time figuring out how many 'things' you will need to do. While doing this you should
158             as regularly as possible call the 'tick' method (any arguments to tick will be ignored in prepare
159             mode). This will, depending on feedback mechanism, trigger some visible indication of
160             'work in progress'.
161            
162             This step is actually
163             optional - maybe you already know how much work you need to do. Or, also common, you really don't know, and
164             it may be either impossible to figure out, or it may be prohibitive to calculate. In this case, you can go
165             straight to calling 'begin'.
166            
167             =item BEING ACTIVE
168            
169             Having called the begin method, you're saying "I'm now actively working with the task".
170            
171             The begin method takes an optional parameter, an integer. The significance is that
172             if you don't pass anything, you're saying 'the extent of this work is unknown, you'll just have to wait...I will
173             call back once in a while to ensure you see me working'. Some feedback presentations
174             are better at portraying this situation than others - for example, a character changing
175             shape for each call will give a good view of this.
176            
177             Passing a number however, constitutes
178             a promise; 'I will call back exactly this number of times' (there's no implication of
179             time between calls though). Again, some presentations look better than others in this
180             situation - specifically, presentations that can show "I'm now here, and so much remains"
181             will then give a fairly clear picture. A typical presentation is the percentage - '85 %'
182             clearly shows that it's almost finished.
183            
184             So, in either of the above cases you should call the 'tick' method. If the total is
185             unknown, each tick simply increments the internal counter by one (i.e. any argument to
186             tick is ignored). If the total is unknown however, you should call with an integer argument.
187             Actually, the integer may be 0 in which case presentations sees it as an 'idle' tick
188             but still may do something visually interesting. Any other integer is simply added to
189             the current count - but beware of calling tick with a number that causes the total to
190             be greater than your promise; this is an error. In any event, this gives the clearest
191             signal to presentations, one which they typically use to calculate amount done vs amount
192             remaining, and then render this in the best way.
193            
194             While 'ticking' is the primary way of informing the user, sometimes it makes sense
195             not only saying "I'm active", but also saying 'I'm currently doing this', i.e. a straightforward
196             message. Messages is a sort of out-of-band communication in regards to ticks. Depending
197             on how the monitor was set up, they may be ignored altogether, written using newlines 'beside'
198             the tick, or perhaps overlaying the tick field(s) (all or in part) - and then automatically
199             time out, restoring the tick fields. Anyway, feel free to give informational messages as
200             needed (but don't assume they'll be seen - just as with ticks, as the monitor in total
201             may be just a black hole).
202            
203             =item FINISHING
204            
205             When all your tasks are complete, you should call the 'end' method. Ideally, you should
206             by now have called tick the right number of times, and thus the final presentation should
207             show (the equivalent to) 100% complete.
208            
209             The monitor is now unusuable for further calls and should be discarded.
210            
211             =item CANCELLATION
212            
213             Preferably, you should intermittently also call 'isCanceled' on the monitor. A true value
214             signals that you should cancel your work at the earliest convenience - if at all possible.
215             I.e. it is legal to not care about the cancellation status. Only you can decide, but it's very
216             nice to allow users to cancel a task.
217            
218             =item SUBTASKS
219            
220             During your prepare or active phase, you may utilize a lower level method/function to
221             do the overall task. In order to still report progress, you need to wrap your monitor inside
222             a SubTask monitor and pass that on.
223            
224             It is illegal to pass on your own monitor - this will break as the lower level method should
225             follow the same pattern detailed here - thus, the first thing called will be 'prepare' and
226             since your own monitor is already in the active phase, it could get very confusing indeed!
227            
228             The pattern here is that you allocate the subtask a certain amount of 'your' progress. Regardless
229             of how many iterations the subtask will do, the progress will be scaled to the parent so that
230             by the time the subtask is 100% complete, it has used up only the allotment it was given.
231            
232             If you think about it, it's clear that this will work for arbitrarily deeply nested
233             subtasks - as long as all methods accept a monitor and use the pattern described here,
234             they can call each other in any order.
235            
236             =back
237            
238             =head1 Available monitor and field types
239            
240             This package provides 4 concrete types that can be used. Two of them are special purpose
241             monitors, and the other two are variations on how to present the feedback as strings.
242            
243             The last two uses 'field' objects to display in different ways (spinner, percentage, bar etc).
244            
245             If this is not adequate for you, it's fairly easy to derive new specializations of either
246             complete monitors or field variations.
247            
248             For each of these, see their respective documentation for details.
249            
250             =head2 MONITOR TYPES
251            
252             =over 2
253            
254             =item ProgressMonitor::Null
255            
256             If you wish to skip feedback, you may instantiate a null monitor. It implements
257             the interface and contract but doesn't do anything with the information.
258            
259             Note: this is commonly used inside monitor accepting methods; if the caller sends no
260             monitor, the code instantiates a null monitor and can then use the monitor
261             interface normally.
262            
263             =item ProgressMonitor::SubTask
264            
265             This is the monitor necessary to wrap a parent monitor for a subtask. This is a
266             concrete implementation and you may use this or call on the monitor for a new
267             suitable instance using 'subMonitor'.
268            
269             =item ProgressMonitor::Stringify::ToStream
270            
271             This monitor type is ideal for simply displaying feedback on stdout, for example.
272             It must be given some field objects which defines the ultimate presentation.
273            
274             =item ProgressMonitor::Stringify::ToCallback
275            
276             This monitor type is useful if you have special needs for displaying, but is still
277             content with a plain string. It acts like ToStream, but instead of printing to the
278             defined stream, it will callback to a code reference you provide.
279            
280             =back
281            
282             =head2 FIELD TYPES
283            
284             =over 2
285            
286             =item ProgressMonitor::Stringify::Fields::Bar
287            
288             This will display a traditional 'bar' that grows from left to right indicating
289             completion amount. If the resolution is too poor to show progress, it will still
290             do something. In cases of an unknown total it will provide a visual indication
291             of movement.
292            
293             =item ProgressMonitor::Stringify::Fields::Counter
294            
295             This will display the common counter, optionally with the total. It will try to show
296             idle progress, but for unknown totals it can only show '?'. It may also overflow if
297             the numbers get to big (settable though, and uncommon mostly).
298            
299             =item ProgressMonitor::Stringify::Fields::ETA
300            
301             This will display an estimated time to completion. When the task is finished, this
302             field will show the actual time it spent (this is very different from other fields as
303             they typically end with the final value, e.g. '100%').
304            
305             Note that since there is nothing
306             forcing progress to happen at a steady beat, this can be a very unreliable estimate.
307             For many tasks however, progress is regular enough to give a reasonable value.
308            
309             =item ProgressMonitor::Stringify::Fields::Fixed
310            
311             This will display a fixed text. Useful for combining with other fields.
312            
313             =item ProgressMonitor::Stringify::Fields::Percentage
314            
315             This will display a percentage showing completion.
316            
317             =item ProgressMonitor::Stringify::Fields::Spinner
318            
319             This will display a 'moving' character/string only. Totals can't be shown with this.
320            
321             =back
322            
323             =head1 SEE ALSO
324            
325             ProgressMonitor is (loosely) based on IProgressMonitor and associated
326             mechanisms in the Eclipse/Java framework. Throw 'IProgressMonitor' into Google
327             and you'll most likely find your way to Eclipse docs around it. Also, this is
328             an article I wrote on how to deal with IProgressMonitor; it is somewhat relevant
329             in this context: L
330            
331             =head1 AUTHOR
332            
333             Kenneth Olwing, C<< >>
334            
335             =head1 BUGS
336            
337             I wouldn't be surprised! If you can come up with a minimal test that shows the
338             problem I might be able to take a look. Even better, send me a patch.
339            
340             Please report any bugs or feature requests to
341             C, or through the web interface at
342             L.
343             I will be notified, and then you'll automatically be notified of progress on
344             your bug as I make changes.
345            
346             =head1 SUPPORT
347            
348             You can find documentation for this module with the perldoc command.
349            
350             perldoc ProgressMonitor
351            
352             You can also look for information at:
353            
354             =over 4
355            
356             =item * AnnoCPAN: Annotated CPAN documentation
357            
358             L
359            
360             =item * CPAN Ratings
361            
362             L
363            
364             =item * RT: CPAN's request tracker
365            
366             L
367            
368             =item * Search CPAN
369            
370             L
371            
372             =back
373            
374             =head1 ACKNOWLEDGEMENTS
375            
376             Thanks to my family. I'm deeply grateful for you!
377            
378             Thanks to the Eclipse project for coming up with the IProgressMonitor interface
379             and surrounding mechanisms.
380            
381             =head1 COPYRIGHT & LICENSE
382            
383             Copyright 2006, 2007, 2008 Kenneth Olwing, all rights reserved.
384            
385             This program is free software; you can redistribute it and/or modify it
386             under the same terms as Perl itself.
387            
388             =cut
389            
390             1; # End of ProgressMonitor