File Coverage

blib/lib/Test/BDD/Cucumber/Model/TagSpec.pm
Criterion Covered Total %
statement 33 34 97.0
branch 13 16 81.2
condition n/a
subroutine 9 9 100.0
pod 1 1 100.0
total 56 60 93.3


line stmt bran cond sub pod time code
1             package Test::BDD::Cucumber::Model::TagSpec;
2             $Test::BDD::Cucumber::Model::TagSpec::VERSION = '0.84';
3             =head1 NAME
4              
5             Test::BDD::Cucumber::Model::TagSpec - Encapsulates tag selectors
6              
7             =head1 VERSION
8              
9             version 0.84
10              
11             =head1 DESCRIPTION
12              
13             Try and deal with the crazy-sauce tagging mechanism in a sane
14             way.
15              
16             =cut
17              
18 19     19   718 use strict;
  19         51  
  19         607  
19 19     19   124 use warnings;
  19         46  
  19         603  
20 19     19   114 use Moo;
  19         53  
  19         113  
21 19     19   6715 use List::Util qw( all any );
  19         75  
  19         1807  
22 19     19   155 use Types::Standard qw( ArrayRef );
  19         58  
  19         172  
23              
24             =head1 OVERVIEW
25              
26             Cucumber tags are all sortsa crazy. This appears to be a direct result of trying
27             to shoe-horn the syntax in to something you can use on the command line. Because
28             'Cucumber' is the name of a gem, application, language, methodology etc etc etc
29             look of disapproval.
30              
31             Here is some further reading on how it's meant to work:
32             L. This is obviously a little
33             insane.
34              
35             Here's how they work here, on a code level: You pass in a list of lists that
36             look like Lisp expressions, with a function: C, C, or C. You can
37             nest these to infinite complexity, but the parser is pretty inefficient, so
38             don't do that. The C function accepts only one argument.
39              
40             I:
41              
42             @important AND @billing: C<<[and => 'important', 'billing']>>
43              
44             (@billing OR @WIP) AND @important: C<<[ and => [ or => 'billing', 'wip' ], 'important' ]>>
45              
46             Skipping both @todo and @wip tags: C<<[ and => [ not => 'todo' ], [ not => 'wip' ] ]>>
47              
48             =head1 ATTRIBUTES
49              
50             =head2 tags
51              
52             An arrayref representing a structure like the above.
53              
54             TagSet->new({
55             tags => [ and => 'green', 'blue', [ or => 'red', 'yellow' ], [ not => 'white' ] ]
56             })
57              
58             =cut
59              
60             has 'tags' => ( is => 'rw', isa => ArrayRef, default => sub { [] } );
61              
62             =head1 METHODS
63              
64             =head2 filter
65              
66             Filter a list of Scenarios by the value of C
67              
68             my @matched = $tagset->filter( @scenarios );
69              
70             If C is empty, no filtering is done.
71              
72             =cut
73              
74             sub filter {
75 14     14 1 6560 my ( $self, @scenarios ) = @_;
76 14 50       26 return @scenarios unless @{ $self->tags };
  14         298  
77              
78             return grep {
79 14         128 my @tags = @{ $_->tags };
  61         105  
  61         1076  
80 61         455 my $scenario = { map { $_ => 1 } @tags };
  143         363  
81              
82 61         995 _matches( $scenario, $self->tags );
83             } @scenarios;
84             }
85              
86             sub _matches {
87 113     113   468 my ( $scenario, $tagspec ) = @_;
88 113         232 my ( $mode, @tags ) = @$tagspec;
89              
90 113 100       262 if ( $mode eq 'and' ) {
    100          
    50          
91             return all {
92 81 100   81   344 ref $_ ? _matches( $scenario, $_ ) : $scenario->{$_}
93 60         644 } @tags;
94             }
95             elsif ( $mode eq 'or' ) {
96             return any {
97 46 100   46   216 ref $_ ? _matches( $scenario, $_ ) : $scenario->{$_}
98 27         91 } @tags;
99             }
100             elsif ( $mode eq 'not' ) {
101 26 50       61 die "'not' expects exactly one tag argument; found @tags"
102             unless @tags == 1;
103              
104             return
105             not (ref $tags[0]
106             ? _matches( $scenario, $tags[0] )
107 26 100       127 : $scenario->{$tags[0]}
108             );
109             }
110             else {
111 0           die "Unexpected tagspec operator '$mode'";
112             }
113             }
114              
115             1;