File Coverage

blib/lib/Articulate/LocationSpecification.pm
Criterion Covered Total %
statement 55 66 83.3
branch 23 42 54.7
condition 2 6 33.3
subroutine 15 16 93.7
pod 6 6 100.0
total 101 136 74.2


line stmt bran cond sub pod time code
1             package Articulate::LocationSpecification;
2 12     12   1220 use strict;
  12         19  
  12         427  
3 12     12   48 use warnings;
  12         15  
  12         332  
4              
5 12     12   94 use Moo;
  12         15  
  12         59  
6 12     12   3021 use Scalar::Util qw(blessed);
  12         15  
  12         1002  
7 12     12   59 use overload '""' => sub { shift->to_file_path }, '@{}' => sub { shift->path };
  12     477   18  
  12     81   105  
  2         5  
  702         1770  
8 12     12   697 use Articulate::Location;
  12         20  
  12         57  
9              
10 12     12   3119 use Exporter::Declare;
  12         16  
  12         60  
11             default_exports qw(new_location_specification);
12              
13             =head1 NAME
14              
15             Articulate::LocationSpecification - represent a specification
16              
17             =cut
18              
19             =head1 DESCRIPTION
20              
21             new_location_specification ['zone', '*', 'article', 'hello-world']
22             new_location_specification 'zone/*/article/hello-world' # same thing
23              
24             An object class which represents a specification - like a 'pattern' or
25             'glob', and provides methods so that it can be compared with locations.
26             It is similar to C, and stringifies to the 'file
27             path' representation.
28              
29             The main use of this is to determine whether a user has access to a
30             resource based on rules (e.g.
31             L).
32              
33             =cut
34              
35             =head1 FUNCTIONS
36              
37             =head3 new_location_specification
38              
39             C is a constructor. It takes either a
40             string (in the form of a path) or an arrayref. Either will be stored as
41             an arrayref in the C attribute.
42              
43             =cut
44              
45             sub new_location_specification {
46 31 50   31 1 1706 if ( 1 == scalar @_ ) {
47 31 50 33     289 if ( blessed $_[0] and $_[0]->isa('Articulate::LocationSpecification') ) {
    50 33        
    100          
    50          
    50          
    0          
48 0         0 return $_[0];
49             }
50             elsif ( blessed $_[0] and $_[0]->isa('Articulate::Location') ) {
51 0         0 my $path = $_[0]->path; # should this logic be in the coerce?
52 0 0       0 if (@$path) {
53 0         0 for my $i ( 1 .. $#$path ) {
54 0 0       0 if ( 0 == ( $i % 2 ) ) {
55 0         0 $path->[$i] = '*';
56             }
57             }
58             }
59 0         0 return __PACKAGE__->new( { path => $path } );
60             }
61             elsif ( ref $_[0] eq 'ARRAY' ) {
62 5         89 return __PACKAGE__->new( { path => $_[0] } );
63             }
64             elsif ( !defined $_[0] ) {
65 0         0 return __PACKAGE__->new;
66             }
67             elsif ( !ref $_[0] ) {
68 78         558 return __PACKAGE__->new(
69 26         109 { path => [ grep { $_ ne '' } split /\//, $_[0] ] } );
70             }
71             elsif ( ref $_[0] eq 'HASH' ) {
72 0         0 return __PACKAGE__->new( $_[0] );
73             }
74             }
75             }
76              
77             =head1 ATTRIBUTE
78              
79             =head3 path
80              
81             An arrayref representing the path to the location specification. This
82             is used for overloaded array dereferencing.
83              
84             =cut
85              
86             has path => (
87             is => 'rw',
88             default => sub { []; },
89             );
90              
91             =head1 METHODS
92              
93             =head3 location
94              
95             $locspec->location->location # same as $locspec
96              
97             This method always returns the object itself.
98              
99             =cut
100              
101             sub location {
102 0     0 1 0 return shift;
103             }
104              
105             =head3 to_file_path
106              
107             Joins the contents of C on C and returns the result. This is
108             used for overloaded stringification.
109              
110             =cut
111              
112             sub to_file_path {
113 2     2 1 2 return join '/', @{ $_[0]->path };
  2         13  
114             }
115              
116             sub _step_matches {
117 350     350   434 my ( $left, $right ) = @_;
118 350 100       882 return 1 if ( $left eq '*' );
119 183 50       281 return 1 if ( $right eq '*' );
120 183 50       594 return 1 if ( $left eq $right );
121 0         0 return 0;
122              
123             }
124              
125             =head3 matches
126              
127             new_location_specification('/zone/*')->matches(new_location('/zone/public')) # true
128             new_location_specification('/zone/*')->matches(new_location('/')) # false
129             new_location_specification('/zone/*')->matches(new_location('/zone/public/article/hello-world')) # false
130              
131             Determines if the location given as the first argument matches the
132             locspec.
133              
134             =cut
135              
136             sub matches {
137 158     158 1 3148 my $self = shift;
138 158         415 my $location = new_location shift;
139 158 100       921 return 0 unless $#$self == $#$location;
140 85 50       126 return 1 if $#$self == -1; # go no further if both are empty
141 85         115 for my $i ( 0 .. $#$self ) {
142 318 50       338 return 0 unless _step_matches( $self->[$i], $location->[$i] );
143             }
144 85         253 return 1;
145             }
146              
147             =head3 matches_ancestor_of
148              
149             new_location_specification('/zone/*')->matches_ancestor_of(new_location('/zone/public')) # true
150             new_location_specification('/zone/*')->matches_ancestor_of(new_location('/')) # false
151             new_location_specification('/zone/*')->matches_ancestor_of(new_location('/zone/public/article/hello-world')) # true
152              
153             Determines if the location given as the first argument - or any
154             ancestor thereof - matches the new_location_specification.
155              
156             =cut
157              
158             sub matches_ancestor_of {
159 7     7 1 2808 my $self = shift;
160 7         22 my $location = new_location shift;
161 7 100       20 return 0 unless $#$self <= $#$location;
162 5 50       28 return 1 if $#$self == -1; # go no further if self is empty
163 5         9 for my $i ( 0 .. $#$self ) {
164 16 50       27 return 0 unless _step_matches( $self->[$i], $location->[$i] );
165             }
166 5         26 return 1;
167             }
168              
169             =head3 matches_descendant_of
170              
171             new_location_specification('/zone/*')->matches_descendant_of(new_location('/zone/public')) # true
172             new_location_specification('/zone/*')->matches_descendant_of(new_location('/')) # true
173             new_location_specification('/zone/*')->matches_descendant_of(new_location('/zone/public/article/hello-world')) # false
174              
175             Determines if the location given as the first argument - or any
176             descendant thereof - matches the new_location_specification.
177              
178             =cut
179              
180             sub matches_descendant_of {
181 7     7 1 1659 my $self = shift;
182 7         21 my $location = new_location shift;
183 7 100       18 return 0 unless $#$self >= $#$location;
184 5 50       10 return 1 if $#$location == -1; # go no further if self is empty
185 5         10 for my $i ( 0 .. $#$location ) {
186 16 50       22 return 0 unless _step_matches( $self->[$i], $location->[$i] );
187             }
188 5         19 return 1;
189             }
190              
191             =head1 SEE ALSO
192              
193             =over
194              
195             =item * L
196              
197             =item * L
198              
199             =item * L
200              
201             =back
202              
203             =cut
204              
205             1;