File Coverage

blib/lib/Articulate/LocationSpecification.pm
Criterion Covered Total %
statement 52 65 80.0
branch 23 42 54.7
condition 2 6 33.3
subroutine 13 15 86.6
pod 6 6 100.0
total 96 134 71.6


line stmt bran cond sub pod time code
1             package Articulate::LocationSpecification;
2 11     11   824 use strict;
  11         13  
  11         391  
3 11     11   48 use warnings;
  11         23  
  11         275  
4              
5 11     11   39 use Moo;
  11         12  
  11         50  
6 11     11   2934 use Scalar::Util qw(blessed);
  11         17  
  11         975  
7 11     11   56 use overload '""' => \&to_file_path, '@{}' => sub { shift->path };
  11     880   16  
  11         100  
  880         2249  
8 11     11   696 use Articulate::Location;
  11         17  
  11         81  
9              
10 11     11   3133 use Exporter::Declare;
  11         20  
  11         49  
11             default_exports qw(locspec);
12              
13             =head1 NAME
14              
15             Articulate::LocationSpecification - represent a specification
16              
17             =cut
18              
19             =head1 DESCRIPTION
20              
21             locspec ['zone', '*', 'article', 'hello-world']
22             locspec '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 locspec
38              
39             C is a constructor. It takes either a string (in the form of a
40             path) or an arrayref. Either will be stored as an arrayref in the
41             C attribute.
42              
43             =cut
44              
45             sub locspec {
46 27 50   27 1 1107 if ( 1 == scalar @_ ) {
47 27 50 33     256 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 4         134 return __PACKAGE__->new( { path => $_[0] } );
63             }
64             elsif ( !defined $_[0] ) {
65 0         0 return __PACKAGE__->new;
66             }
67             elsif ( !ref $_[0] ) {
68 70         458 return __PACKAGE__->new(
69 23         92 { 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 0     0 1 0 return join '/', @{ $_[0]->path };
  0         0  
114             }
115              
116             sub _step_matches {
117 438     438   473 my ( $left, $right ) = @_;
118 438 100       980 return 1 if ( $left eq '*' );
119 228 50       327 return 1 if ( $right eq '*' );
120 228 50       654 return 1 if ( $left eq $right );
121 0         0 return 0;
122              
123             }
124              
125             =head3 matches
126              
127             locspec('/zone/*')->matches(loc('/zone/public')) # true
128             locspec('/zone/*')->matches(loc('/')) # false
129             locspec('/zone/*')->matches(loc('/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 202     202 1 1566 my $self = shift;
138 202         439 my $location = loc shift;
139 202 100       1627 return 0 unless $#$self == $#$location;
140 108 50       132 return 1 if $#$self == -1; # go no further if both are empty
141 108         146 for my $i ( 0 .. $#$self ) {
142 406 50       407 return 0 unless _step_matches( $self->[$i], $location->[$i] );
143             }
144 108         309 return 1;
145             }
146              
147             =head3 matches_ancestor_of
148              
149             locspec('/zone/*')->matches_ancestor_of(loc('/zone/public')) # true
150             locspec('/zone/*')->matches_ancestor_of(loc('/')) # false
151             locspec('/zone/*')->matches_ancestor_of(loc('/zone/public/article/hello-world')) # true
152              
153             Determines if the location given as the first argument - or any
154             ancestor thereof - matches the locspec.
155              
156             =cut
157              
158             sub matches_ancestor_of {
159 7     7 1 1963 my $self = shift;
160 7         16 my $location = loc shift;
161 7 100       15 return 0 unless $#$self <= $#$location;
162 5 50       6 return 1 if $#$self == -1; # go no further if self is empty
163 5         6 for my $i ( 0 .. $#$self ) {
164 16 50       16 return 0 unless _step_matches( $self->[$i], $location->[$i] );
165             }
166 5         14 return 1;
167             }
168              
169             =head3 matches_descendant_of
170              
171             locspec('/zone/*')->matches_descendant_of(loc('/zone/public')) # true
172             locspec('/zone/*')->matches_descendant_of(loc('/')) # true
173             locspec('/zone/*')->matches_descendant_of(loc('/zone/public/article/hello-world')) # false
174              
175             Determines if the location given as the first argument - or any
176             descendant thereof - matches the locspec.
177              
178             =cut
179              
180             sub matches_descendant_of {
181 7     7 1 787 my $self = shift;
182 7         29 my $location = loc shift;
183 7 100       14 return 0 unless $#$self >= $#$location;
184 5 50       8 return 1 if $#$location == -1; # go no further if self is empty
185 5         9 for my $i ( 0 .. $#$location ) {
186 16 50       18 return 0 unless _step_matches( $self->[$i], $location->[$i] );
187             }
188 5         13 return 1;
189             }
190              
191             =head1 SEE ALSO
192              
193             =over
194              
195             =item * L
196              
197             =item * L
198              
199             =back
200              
201             =cut
202              
203             1;