File Coverage

lib/Badger/Reporter.pm
Criterion Covered Total %
statement 9 140 6.4
branch 0 70 0.0
condition 0 35 0.0
subroutine 3 25 12.0
pod 1 20 5.0
total 13 290 4.4


line stmt bran cond sub pod time code
1             package Badger::Reporter;
2              
3             use Badger::Class
4 1         8 version => 0.01,
5             debug => 0,
6             base => 'Badger::Base',
7             import => 'class',
8             config => 'verbose=0 quiet=0 nothing|dryrun=0 progress=0 colour|color=1 progress_module|method:PROGRESS_MODULE',
9             utils => 'self_params params xprintf',
10             auto_can => 'auto_can',
11             constants => 'ARRAY HASH BLANK DELIMITER',
12             constant => {
13             NO_REASON => 'no reason given',
14             PROGRESS_MODULE => 'Badger::Progress',
15             },
16             messages => {
17             bad_colour => 'Invalid colour specified for %s event: %s',
18 1     1   6 };
  1         1  
19              
20 1     1   6 use Badger::Debug ':dump';
  1         1  
  1         3  
21             use Badger::Rainbow
22 1         4 ANSI => 'all',
23 1     1   6 import => 'strip_ANSI_escapes';
  1         1  
24              
25             our $COLOURS = {
26             bold => \&bold,
27             dark => \&dark,
28             black => \&black,
29             red => \&red,
30             green => \&green,
31             blue => \&blue,
32             cyan => \&cyan,
33             magenta => \&magenta,
34             yellow => \&yellow,
35             grey => \&grey,
36             white => \&white,
37             };
38              
39              
40              
41             #-----------------------------------------------------------------------
42             # init methods
43             #-----------------------------------------------------------------------
44              
45             sub init {
46 0     0 1   my ($self, $config) = @_;
47 0           $self->configure($config)
48             ->init_events($config)
49             ->init_reporter($config);
50 0           return $self;
51             }
52              
53              
54             sub init_events {
55 0     0 0   my ($self, $config) = @_;
56 0           my $lookup = $self->{ event } = { };
57 0           my $events = $self->{ events } = [ ];
58 0           my $names = $self->{ event_names } = [ ];
59 0           my ($evspec, $event, $name);
60              
61 0           $self->debug("init_events()") if DEBUG;
62              
63             # events can be specified as a list ref of 'whitespace delimited string'
64 0   0       $evspec = $config->{ events } || [ ];
65 0 0         $evspec = [ split(DELIMITER, $evspec) ]
66             unless ref $evspec eq ARRAY;
67              
68 0           $self->debug("event spec: $evspec ==> ", $self->dump_data($evspec)) if DEBUG;
69              
70             # now merge it with any events specifed in $EVENTS class variable(s)
71 0           $evspec = $self->class->list_vars( EVENTS => $evspec );
72              
73 0           $self->debug("event spec: ", $self->dump_data($evspec)) if DEBUG;
74              
75 0           foreach (@$evspec) {
76 0           $self->debug("event: $_") if DEBUG;
77 0           $event = $_; # avoid aliasing
78 0 0         $event = { name => $event }
79             unless ref $event eq HASH;
80             $name = $event->{ name }
81 0   0       || return $self->error_msg( missing => 'event name' );
82              
83             # set some defaults
84 0 0         $event->{ message } = '%s' unless defined $event->{ message };
85 0 0         $event->{ summary } = '%s %s' unless defined $event->{ summary };
86              
87             # TODO: is ignoring duplicates the right thing to do?
88 0 0         next if $lookup->{ $name };
89              
90 0           push(@$names, $name);
91 0           push(@$events, $event);
92 0           $lookup->{ $name } = $event;
93             }
94              
95 0           $self->debug("initalised events: ", $self->dump_data($lookup)) if DEBUG;
96              
97 0           return $self;
98             }
99              
100              
101             sub init_reporter {
102 0     0 0   my ($self, $config) = @_;
103 0           $self->init_stats;
104 0           $self->init_output;
105             }
106              
107              
108             sub init_stats {
109 0     0 0   my $self = shift;
110 0           $self->{ count } = 0;
111             $self->{ stats } = {
112 0           map { $_ => 0 }
  0            
113             $self->event_names
114             };
115 0           return $self;
116             }
117              
118              
119             sub init_output {
120 0     0 0   my ($self, $config) = @_;
121 0           my ($event, $cols, $col, $colname);
122              
123             # fetch a hash table for all the colo(u)rs we know about
124             $cols = $self->{ colours } ||= $self->class->hash_vars(
125             COLOURS => $config->{ colours } || $config->{ colors }
126 0   0       );
      0        
127              
128 0 0         if ($self->{ colour }) {
129 0           foreach $event ($self->events) {
130             # if the event specifies a colour then change the 'message' and
131             # 'summary' output formats to include ANSI escape sequences
132 0 0 0       if ($colname = $event->{ colour } || $event->{ color }) {
133             $col = $cols->{ $colname }
134 0   0       || return $self->error_msg( bad_colour => $event->{ name } => $colname );
135 0           for (qw( message summary )) {
136 0 0         $event->{ $_ } = $col->($event->{ $_ }) if $event->{ $_ };
137             }
138             }
139             }
140             }
141             else {
142             # strip any colour that might have been previously added
143 0           foreach $event ($self->events) {
144 0           $event->{ message } = strip_ANSI_escapes($event->{ message });
145 0           $event->{ summary } = strip_ANSI_escapes($event->{ summary });
146             }
147             }
148              
149 0           return $self;
150             }
151              
152              
153             sub init_progress {
154 0     0 0   my ($self, $params) = self_params(@_);
155 0           my $module = $self->{ progress_module };
156 0           class($module)->load;
157 0           return $self->{ progress_counter } = $module->new($params);
158             }
159              
160             sub progress {
161 0     0 0   my $self = shift;
162             return $self->{ progress_counter }
163 0   0       ||= $self->init_progress(@_);
164             }
165              
166             sub tick {
167 0     0 0   my $self = shift;
168 0   0       my $progress = $self->{ progress_counter } || return;
169 0 0         return if $self->{ verbose };
170 0           print $progress->pixel;
171             }
172              
173             sub tock {
174 0     0 0   my $self = shift;
175 0   0       my $progress = $self->{ progress_counter } || return;
176 0 0         return if $self->{ verbose };
177 0           print $progress->remains;
178             }
179              
180             #-----------------------------------------------------------------------
181             # accessor methods
182             #-----------------------------------------------------------------------
183              
184             sub event {
185 0     0 0   my $self = shift;
186             # TODO: If we allow events to be added then we should also add them to
187             # the events/name list. That suggests that init_events() needs to be
188             # cleaved in twain so that we can re-used the event adding code without
189             # having to go through the full configuration process which expects a
190             # config and merges events from the $EVENTS package variable(s).
191             return @_
192             ? $self->{ event }->{ $_[0] }
193 0 0         : $self->{ event };
194             }
195              
196              
197             sub events {
198 0     0 0   my $self = shift;
199 0           my $events = $self->{ events };
200             return wantarray
201 0 0         ? @$events
202             : $events;
203             }
204              
205              
206             sub event_names {
207 0     0 0   my $self = shift;
208 0           my $names = $self->{ event_names };
209             return wantarray
210 0 0         ? @$names
211             : $names;
212             }
213              
214              
215             #-----------------------------------------------------------------------
216             # basic reporting methods
217             #-----------------------------------------------------------------------
218              
219             sub report {
220 0     0 0   my $self = shift;
221 0   0       my $type = shift
222             || return $self->error_msg( missing => 'event type' );
223 0   0       my $event = $self->{ event }->{ $type }
224             || return $self->error_msg( invalid => 'event type' => $type );
225              
226             # TODO: Why don't we store the stats in the event? Saves splitting
227             # things up...
228 0           $self->{ stats }->{ $type }++;
229 0           $self->{ count }++;
230              
231             # If we're running in quiet mode, or if the event describes itself as
232             # being verbose and we're not running in verbose mode, then we return
233             # now. We also return if the event doesn't have a message format.
234 0 0         return if $self->{ quiet };
235 0           $self->tick;
236 0 0 0       return if $event->{ verbose } && ! $self->{ verbose };
237 0 0         return unless $event->{ message };
238              
239 0           $self->say( xprintf($event->{ message }, @_) );
240              
241 0           return $event->{ return }; # usually undef
242             }
243              
244              
245             sub say_msg {
246 0     0 0   my $self = shift;
247 0           print $self->message(@_), "\n";
248             }
249              
250              
251             sub say {
252 0     0 0   my $self = shift;
253 0           print @_, "\n";
254             }
255              
256              
257              
258              
259             #-----------------------------------------------------------------------
260             # auto_can method generator
261             #-----------------------------------------------------------------------
262              
263             sub auto_can {
264 0     0 0   my ($self, $name) = @_;
265 0           my $event;
266              
267 0           $self->debug("auto_can($name)") if DEBUG;
268              
269 0 0 0       if ($name =~ s/_msg$// && ($event = $self->{ event }->{ $name })) {
    0          
270             return sub {
271 0     0     my $self = shift;
272 0           $self->report( $name => $self->message(@_) );
273             }
274 0           }
275             elsif ($event = $self->{ event }->{ $name }) {
276             return sub {
277 0     0     shift->report( $name => @_ );
278             }
279 0           }
280 0           elsif (DEBUG) {
281             $self->debug("$name is not an event in ", $self->dump_data($self->{ event }));
282             }
283 0           return undef;
284             }
285              
286              
287              
288             #-----------------------------------------------------------------------
289             # summary
290             #-----------------------------------------------------------------------
291              
292             sub summary {
293 0     0 0   my $self = shift;
294 0           my $stats = $self->{ stats };
295 0           my ($event, $name, $format, $count, @output);
296              
297 0           $self->debug("summary of stats: ", $self->dump_data($stats)) if DEBUG;
298              
299             # TODO: no point worrying about being quiet if we're going to say it
300 0 0         unless ($self->{ quiet }) {
301 0           foreach $event ($self->events) {
302 0 0         next unless $format = $event->{ summary };
303 0           $name = $event->{ name };
304 0 0         next unless $count = $stats->{ $name };
305 0 0         push(@output, xprintf($format, $count, $count == 1 ? '' : 's', $name) );
306             }
307             }
308              
309             # $self->init_stats;
310              
311 0           return join("\n", @output);
312             }
313              
314              
315              
316             #-----------------------------------------------------------------------
317             # Command line argument parser and help/usage for scripts to use.
318             # This is a quick hack until Badger::Config is finished.
319             #-----------------------------------------------------------------------
320              
321             sub configure_args {
322 0     0 0   my $self = shift;
323 0 0 0       my @args = @_ == 1 && ref $_[0] eq ARRAY ? @{$_[0]}
  0 0          
324             : @_ ? @_
325             : @ARGV;
326              
327 0           $self->debug("configure_args(", $self->dump_data(\@args)) if DEBUG;
328              
329 0 0         return $self->usage if grep(/--?h(elp)?/, @args);
330 0 0         $self->{ dryrun } = 1 if grep(/--?(n(othing)?|dry[-_]?run)/, @args);
331 0 0         $self->{ verbose } = 1 if grep(/--?v(erbose)?/, @args);
332 0 0         $self->{ quiet } = 1 if grep(/--?q(uiet)?/, @args);
333 0 0         $self->{ colour } = 1 if grep(/--?c(olou?r)?/, @args);
334 0 0         $self->{ summary } = 1 if grep(/--?s(ummary)?/, @args);
335 0 0         $self->{ progress } = 1 if grep(/--?p(rogress)?/, @args);
336              
337             # Get any extra configuration from the subclass scheme definition
338             # NOTE: This only works in immediate subclasses. A more thorough
339             # implementation should call list_vars() and deal with everything,
340             # thereby eliminating the above code. However, that's something for
341             # Badger::Config
342 0           my $config = $self->class->list_vars('CONFIG'); # may overwrite above
343 0 0         if ($config) {
344 0           foreach my $item (@$config) {
345 0           my $name = quotemeta $item->{ name };
346 0 0         $self->{ $name } = 1 if grep(/--?$name/, @args);
347 0           if (DEBUG) {
348             $self->debug("CONFIG $name => ", defined($self->{ name }) ? $self->{ name } : '');
349             }
350             }
351             }
352              
353 0 0         $self->{ colour } = 0 if grep(/--?no[-_]?c(olou?r)?/, @args);
354 0 0         $self->{ colour } = 0 if grep(/--?white/, @args);
355              
356 0           $self->init_output;
357              
358 0           return $self;
359             }
360              
361              
362              
363              
364             sub usage {
365 0     0 0   my $options = shift->options_summary;
366 0           die <
367             $0 [options]
368              
369             Options:
370             $options
371             EOF
372             }
373              
374             sub options_summary {
375 0     0 0   return <
376             -h --help This help
377             -v --verbose Verbose mode (extra output)
378             -p --progress Progress mode
379             -q --quiet Quiet mode (no output)
380             -s --summary Print summary at end
381             -n --nothing --dry-run Dry run - no action performed
382             -c --colour --color Colourful output
383             -nc --no-colour --no-color Uncolourful output
384             EOF
385             }
386              
387              
388             1;