File Coverage

blib/lib/Path/Dispatcher/Rule/Tokens.pm
Criterion Covered Total %
statement 59 73 80.8
branch 15 30 50.0
condition 13 15 86.6
subroutine 10 11 90.9
pod 0 3 0.0
total 97 132 73.4


line stmt bran cond sub pod time code
1             package Path::Dispatcher::Rule::Tokens;
2             # ABSTRACT: predicate is a list of tokens
3              
4             our $VERSION = '1.07';
5              
6 32     32   72678 use Moo;
  32         10658  
  32         182  
7 32     32   12162 use Types::Standard qw(Str ArrayRef Bool);
  32         75593  
  32         232  
8              
9             extends 'Path::Dispatcher::Rule';
10              
11             has tokens => (
12             is => 'ro',
13             isa => ArrayRef,
14             required => 1,
15             );
16              
17             has delimiter => (
18             is => 'ro',
19             isa => Str,
20             default => ' ',
21             );
22              
23             has case_sensitive => (
24             is => 'ro',
25             isa => Bool,
26             default => 1,
27             );
28              
29             sub _match_as_far_as_possible {
30 84     84   131 my $self = shift;
31 84         117 my $path = shift;
32              
33 84         221 my @got = $self->tokenize($path->path);
34 84         154 my @expected = @{ $self->tokens };
  84         229  
35 84         131 my @matched;
36              
37 84   100     358 while (@got && @expected) {
38 102         185 my $expected = $expected[0];
39 102         146 my $got = $got[0];
40              
41 102 100       201 last unless $self->_match_token($got, $expected);
42              
43 62         255 push @matched, $got;
44 62         200 shift @expected;
45 62         288 shift @got;
46             }
47              
48 84         380 return (\@matched, \@got, \@expected);
49             }
50              
51             sub _match {
52 84     84   128 my $self = shift;
53 84         123 my $path = shift;
54              
55 84         189 my ($matched, $got, $expected) = $self->_match_as_far_as_possible($path);
56              
57 84 100       284 return if @$expected; # didn't provide everything necessary
58 43 100 100     164 return if @$got && !$self->prefix; # had tokens left over
59              
60 39         102 my $leftover = $self->untokenize(@$got);
61              
62 39 50       103 return if !$matched;
63              
64             return {
65 39         185 positional_captures => $matched,
66             leftover => $leftover,
67             };
68             }
69              
70             sub complete {
71 0     0 0 0 my $self = shift;
72 0         0 my $path = shift;
73              
74 0         0 my ($matched, $got, $expected) = $self->_match_as_far_as_possible($path);
75 0 0       0 return if @$got > 1; # had tokens leftover
76 0 0       0 return if !@$expected; # consumed all tokens
77              
78 0         0 my $next = shift @$expected;
79 0 0       0 my $part = @$got ? shift @$got : '';
80 0         0 my @completions;
81              
82 0 0       0 for my $completion (ref($next) eq 'ARRAY' ? @$next : $next) {
83 0 0       0 next if ref($completion);
84              
85 0 0       0 next unless substr($completion, 0, length($part)) eq $part;
86 0         0 push @completions, $self->untokenize(@$matched, $completion);
87             }
88              
89 0         0 return @completions;
90             }
91              
92             sub _each_token {
93 118     118   186 my $self = shift;
94 118         177 my $got = shift;
95 118         154 my $expected = shift;
96 118         177 my $callback = shift;
97              
98 118 100 66     377 if (ref($expected) eq 'ARRAY') {
    50          
99 11         21 for my $alternative (@$expected) {
100 16         33 $self->_each_token($got, $alternative, $callback);
101             }
102             }
103             elsif (!ref($expected) || ref($expected) eq 'Regexp') {
104 107         223 $callback->($got, $expected);
105             }
106             else {
107 0         0 die "Unexpected token '$expected'"; # the irony is not lost on me :)
108             }
109             }
110              
111             sub _match_token {
112 102     102   157 my $self = shift;
113 102         154 my $got = shift;
114 102         144 my $expected = shift;
115              
116 102         158 my $matched = 0;
117             $self->_each_token($got, $expected, sub {
118 107     107   220 my ($g, $e) = @_;
119 107 100       263 if (!ref($e)) {
    50          
120 102 100       260 ($g, $e) = (lc $g, lc $e) if !$self->case_sensitive;
121 102   100     393 $matched ||= $g eq $e;
122             }
123             elsif (ref($e) eq 'Regexp') {
124 5   66     35 $matched ||= $g =~ $e;
125             }
126 102         519 });
127              
128 102         537 return $matched;
129             }
130              
131             sub tokenize {
132 84     84 0 128 my $self = shift;
133 84         133 my $path = shift;
134 84         1007 return grep { length } split $self->delimiter, $path;
  171         483  
135             }
136              
137             sub untokenize {
138 39     39 0 65 my $self = shift;
139 39         86 my @tokens = @_;
140             return join $self->delimiter,
141 24         85 grep { length }
142 39         120 map { split $self->delimiter, $_ }
  24         223  
143             @tokens;
144             }
145              
146             __PACKAGE__->meta->make_immutable;
147 32     32   50671 no Moo;
  32         90  
  32         181  
148              
149             1;
150              
151             __END__