File Coverage

lib/Test/BDD/Cucumber/Harness/TermColor.pm
Criterion Covered Total %
statement 81 113 71.6
branch 18 36 50.0
condition 10 22 45.4
subroutine 17 18 94.4
pod 6 6 100.0
total 132 195 67.6


line stmt bran cond sub pod time code
1             package Test::BDD::Cucumber::Harness::TermColor;
2             $Test::BDD::Cucumber::Harness::TermColor::VERSION = '0.84';
3             =head1 NAME
4              
5             Test::BDD::Cucumber::Harness::TermColor - Prints colorized text to the screen
6              
7             =head1 VERSION
8              
9             version 0.84
10              
11             =head1 DESCRIPTION
12              
13             A L subclass that prints test output, colorized,
14             to the terminal.
15              
16             =head1 CONFIGURABLE ENV
17              
18             =head2 ANSI_COLORS_DISABLED
19              
20             You can use L's C to turn off colors
21             in the output.
22              
23             =cut
24              
25 2     2   1135 use strict;
  2         4  
  2         63  
26 2     2   12 use warnings;
  2         4  
  2         47  
27 2     2   10 use Moo;
  2         6  
  2         13  
28 2     2   660 use Types::Standard qw( Str HashRef FileHandle );
  2         5  
  2         14  
29              
30 2     2   2423 use Getopt::Long;
  2         10769  
  2         14  
31              
32             # Try and make the colors just work on Windows...
33             BEGIN {
34 2 0 33 2   547 if (
      33        
35             # We're apparently on Windows
36             $^O =~ /MSWin32/i &&
37              
38             # We haven't disabled coloured output for Term::ANSIColor
39             ( !$ENV{'ANSI_COLORS_DISABLED'} ) &&
40              
41             # Here's a flag you can use if you really really need to turn this fall-
42             # back behaviour off
43             ( !$ENV{'DISABLE_WIN32_FALLBACK'} )
44             )
45             {
46             # Try and load
47 0         0 eval { require Win32::Console::ANSI };
  0         0  
48 0 0       0 if ($@) {
49 0         0 print "# Install Win32::Console::ANSI to display colors properly\n";
50             }
51             }
52             }
53              
54 2     2   1330 use Term::ANSIColor;
  2         19199  
  2         165  
55 2     2   16 use Test::BDD::Cucumber::Model::Result;
  2         4  
  2         3169  
56              
57             extends 'Test::BDD::Cucumber::Harness';
58              
59             =head1 CONFIGURABLE ATTRIBUTES
60              
61             =head2 fh
62              
63             A filehandle to write output to; defaults to C
64              
65             =cut
66              
67             has 'fh' => ( is => 'rw', isa => FileHandle, default => sub { \*STDOUT } );
68              
69             =head2 theme
70              
71             Name of the theme to use for colours. Defaults to `dark`. Themes are defined
72             in the private attribute C<_themes>, and currently include `light` and `dark`
73              
74             =cut
75              
76             has theme => (
77             'is' => 'ro',
78             isa => Str,
79             lazy => 1,
80             default => sub {
81             my $theme = 'dark';
82             Getopt::Long::Configure('pass_through');
83             GetOptions( "c|theme=s" => \$theme );
84             return ($theme);
85             }
86             );
87              
88             has _themes => (
89             is => 'ro',
90             isa => HashRef[HashRef],
91             lazy => 1,
92             default => sub {
93             {
94             dark => {
95             'feature' => 'bright_white',
96             'scenario' => 'bright_white',
97             'scenario_name' => 'bright_blue',
98             'pending' => 'yellow',
99             'passing' => 'green',
100             'failed' => 'red',
101             'step_data' => 'bright_cyan',
102             },
103             light => {
104             'feature' => 'reset',
105             'scenario' => 'black',
106             'scenario_name' => 'blue',
107             'pending' => 'yellow',
108             'passing' => 'green',
109             'failed' => 'red',
110             'step_data' => 'magenta',
111             },
112             };
113             }
114             );
115              
116             sub _colors {
117 7     7   22 my $self = shift;
118 7   50     126 return $self->_themes->{ $self->theme }
119             || die( 'Unknown color theme [' . $self->theme . ']' );
120             }
121              
122             my $margin = 2;
123             my $current_feature;
124              
125             sub feature {
126 1     1 1 5 my ( $self, $feature ) = @_;
127 1         21 my $fh = $self->fh;
128              
129 1         9 $current_feature = $feature;
130             $self->_display(
131             {
132             indent => 0,
133             color => $self->_colors->{'feature'},
134             text => $feature->keyword_original . ': ' . ( $feature->name || '' ),
135             follow_up =>
136 1 50 50     5 [ map { $_->content } @{ $feature->satisfaction || [] } ],
  0         0  
  1         107  
137             trailing => 1
138             }
139             );
140             }
141              
142             sub feature_done {
143 1     1 1 2 my $self = shift;
144 1         19 my $fh = $self->fh;
145 1         8 print $fh "\n";
146             }
147              
148             sub scenario {
149 1     1 1 6 my ( $self, $scenario, $dataset, $longest ) = @_;
150             my $text =
151             $scenario->keyword_original . ': '
152 1   50     23 . color( $self->_colors->{'scenario_name'} )
153             . ( $scenario->name || '' );
154              
155             $self->_display(
156             {
157             indent => 2,
158             color => $self->_colors->{'scenario'},
159             text => $text,
160             follow_up =>
161 1 50 50     80 [ map { $_->content } @{ $scenario->description || [] } ],
  0         0  
  1         44  
162             trailing => 0,
163             longest_line => ( $longest || 0 )
164             }
165             );
166             }
167              
168             sub scenario_done {
169 1     1 1 3 my $self = shift;
170 1         35 my $fh = $self->fh;
171 1         11 print $fh "\n";
172             }
173              
174       2 1   sub step { }
175              
176             sub step_done {
177 2     2 1 7 my ( $self, $context, $result, $highlights ) = @_;
178              
179 2         4 my $color;
180 2         4 my $follow_up = [];
181 2         5 my $status = $result->result;
182 2         5 my $failed = 0;
183              
184 2 100 66     13 if ( $status eq 'undefined' || $status eq 'pending' ) {
    50          
185 1         3 $color = $self->_colors->{'pending'};
186             } elsif ( $status eq 'passing' ) {
187 1         6 $color = $self->_colors->{'passing'};
188             } else {
189 0         0 $failed = 1;
190 0         0 $color = $self->_colors->{'failed'};
191 0         0 $follow_up = [ split( /\n/, $result->{'output'} ) ];
192              
193 0 0       0 if ( !$context->is_hook ) {
194 0         0 unshift @{$follow_up},
  0         0  
195             'step defined at '
196             . $context->step->line->document->filename
197             . ' line '
198             . $context->step->line->number . '.';
199             }
200             }
201              
202 2         71 my $text;
203              
204 2 50       36 if ( $context->is_hook ) {
    100          
205 0 0       0 $failed or return;
206 0         0 $text = 'In ' . ucfirst( $context->verb ) . ' Hook';
207 0         0 undef $highlights;
208             } elsif ($highlights) {
209 1         57 $text = $context->step->verb_original . ' ' . $context->text;
210 1         28 $highlights =
211             [ [ 0, $context->step->verb_original . ' ' ], @$highlights ];
212             } else {
213 1         40 $text = $context->step->verb_original . ' ' . $context->text;
214 1         13 $highlights = [ [ 0, $text ] ];
215             }
216              
217             $self->_display(
218             {
219             indent => 4,
220             color => $color,
221             text => $text,
222             highlights => $highlights,
223             highlight => $self->_colors->{'step_data'},
224             trailing => 0,
225             follow_up => $follow_up,
226 2         16 longest_line => $context->stash->{'scenario'}->{'longest_step_line'}
227             }
228             );
229              
230 2         12 $self->_note_step_data( $context->step );
231             }
232              
233             sub _note_step_data {
234 2     2   6 my ( $self, $step ) = @_;
235 2 50       6 return unless $step;
236 2         4 my @step_data = @{ $step->data_as_strings };
  2         46  
237 2 50       22 return unless @step_data;
238              
239             my $note = sub {
240 0     0   0 my ( $text, $extra_indent ) = @_;
241 0   0     0 $extra_indent ||= 0;
242              
243             $self->_display(
244             {
245             indent => 6 + $extra_indent,
246 0         0 color => $self->_colors->{'step_data'},
247             text => $text
248             }
249             );
250 0         0 };
251              
252 0 0       0 if ( ref( $step->data ) eq 'ARRAY' ) {
253 0         0 for (@step_data) {
254 0         0 $note->($_);
255             }
256             } else {
257 0         0 $note->('"""');
258 0         0 for (@step_data) {
259 0         0 $note->( $_, 2 );
260             }
261 0         0 $note->('"""');
262             }
263             }
264              
265             sub _display {
266 4     4   140 my ( $class, $options ) = @_;
267 4 50       102 my $fh = ref $class ? $class->fh : \*STDOUT;
268 4         33 $options->{'indent'} += $margin;
269              
270             # Reset it all...
271 4         26 print $fh color 'reset';
272              
273             # Print the main line
274 4         186 print $fh ' ' x $options->{'indent'};
275              
276             # Highlight as appropriate
277 4         47 my $color = color $options->{'color'};
278 4 100 66     93 if ( $options->{'highlight'} && $options->{'highlights'} ) {
279 2         8 my $reset = color 'reset';
280 2         36 my $base = color $options->{'color'};
281 2         38 my $hl = color $options->{'highlight'};
282              
283 2         33 for ( @{ $options->{'highlights'} } ) {
  2         7  
284 10         108 my ( $flag, $text ) = @$_;
285 10 100       39 print $fh $reset . ( $flag ? $hl : $base ) . $text . $reset;
286             }
287              
288             # Normal output
289             } else {
290 2         8 print $fh color $options->{'color'};
291 2         56 print $fh $options->{'text'};
292             }
293              
294             # Reset and newline
295 4         49 print $fh color 'reset';
296 4         112 print $fh "\n";
297              
298             # Print follow-up lines...
299 4 50       40 for my $line ( @{ $options->{'follow_up'} || [] } ) {
  4         14  
300 0         0 print $fh color 'reset';
301 0         0 print $fh ' ' x ( $options->{'indent'} + 4 );
302 0         0 print $fh color $options->{'color'};
303 0         0 print $fh $line;
304 0         0 print $fh color 'reset';
305 0         0 print $fh "\n";
306             }
307              
308 4 100       17 print $fh "\n" if $options->{'trailing'};
309             }
310              
311             =head1 AUTHOR
312              
313             Peter Sergeant C
314              
315             =head1 LICENSE
316              
317             Copyright 2019-2023, Erik Huelsmann
318             Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl
319              
320             =cut
321              
322             1;