File Coverage

blib/lib/Path/AttrRouter/DispatchType/Chained.pm
Criterion Covered Total %
statement 75 114 65.7
branch 35 64 54.6
condition 2 10 20.0
subroutine 10 11 90.9
pod 0 5 0.0
total 122 204 59.8


line stmt bran cond sub pod time code
1             package Path::AttrRouter::DispatchType::Chained;
2 7     7   5440 use Mouse;
  7         43  
  7         49  
3              
4 7     7   2348 use Carp;
  7         15  
  7         580  
5 7     7   46 use File::Spec::Unix;
  7         11  
  7         1134  
6              
7             has name => (
8             is => 'rw',
9             isa => 'Str',
10             default => 'Chained',
11             );
12              
13             has chain_from => (
14             is => 'rw',
15             isa => 'HashRef',
16             default => sub { {} },
17             );
18              
19             has endpoints => (
20             is => 'rw',
21             isa => 'ArrayRef',
22             default => sub { [] },
23             );
24              
25             has actions => (
26             is => 'rw',
27             isa => 'HashRef',
28             default => sub { {} },
29             );
30              
31 7     7   43 no Mouse;
  7         14  
  7         40  
32              
33             sub match {
34 50     50 0 66 my ($self, $condition) = @_;
35 50 100       89 return if @{$condition->{args}};
  50         150  
36              
37 30         103 my @parts = split '/', $condition->{path};
38              
39 30         77 my ($chain, $action_captures, $parts) = $self->recurse_match($condition, '/', @parts);
40 30 100       98 return unless $chain;
41              
42 14         18 @{$condition->{args}} = @$parts;
  14         26  
43 14         15 @{$condition->{captures}} = @$action_captures;
  14         24  
44              
45 14         44 return $condition->{action_class}->from_chain($chain);
46             }
47              
48             sub recurse_match {
49 47     47 0 232 my ($self, $condition, $parent, @pathparts) = @_;
50              
51 47 100       52 my @chains = @{ $self->chain_from->{ $parent } || [] }
  47 100       303  
52             or return;
53              
54 31         43 for my $action (@chains) {
55 80         135 my @parts = @pathparts;
56              
57 80         220 my $pathpart = $action->attributes->{PathPart}[0];
58 80 100       141 if (length $pathpart) {
59 77         128 my @p = split '/', $pathpart;
60 77 50       4625 next if @p > @parts;
61              
62 77         117 my @stripped = splice @parts, 0, scalar @p;
63 77 100       261 next unless $pathpart eq join '/', @stripped;
64             }
65              
66 44 100       131 if (defined $action->attributes->{CaptureArgs}[0]) {
67 17         39 my $capture_args = $action->attributes->{CaptureArgs}[0];
68 17 50       39 next if @parts < $capture_args;
69              
70 17         24 my @captures = splice @parts, 0, $capture_args;
71 17         77 my ($actions, $captures, $action_parts)
72             = $self->recurse_match($condition, '/'.$action->reverse, @parts);
73 17 50       34 next unless $actions;
74              
75 17         88 return ([ $action, @$actions ], [@captures, @$captures], $action_parts);
76             }
77             else {
78 27 100       156 next unless $action->match({%$condition, args => \@parts});
79 14         75 return ([ $action ], [], \@parts);
80             }
81             }
82             }
83              
84             sub register {
85 52     52 0 71 my ($self, $action) = @_;
86              
87 52 100       56 my @chained = @{ $action->attributes->{Chained} || [] }
  52 100       452  
88             or return;
89              
90 18         24 my $parent = $chained[0];
91 18 50       29 if ($parent) {
92 18 50       65 unless ($parent =~ m!^/!) {
93 0         0 $parent = File::Spec::Unix->rel2abs($parent, '/' . $action->namespace);
94             }
95             }
96             else {
97 0         0 $parent = '/';
98             }
99 18         53 $action->attributes->{Chained} = [$parent];
100              
101 18   100     101 my $children = $self->chain_from->{ $parent } ||= [];
102 18 100       22 my @pathpart = @{ $action->attributes->{PathPart} || [] };
  18         76  
103              
104 18 100       46 my $part = defined $pathpart[0] ? $pathpart[0] : $action->name;
105 18         53 $action->attributes->{PathPart} = [$part];
106              
107             my $num_parts = sub {
108 132     132   146 my $action = $_[0];
109 132         344 my @parts = split '/', $action->attributes->{PathPart}[0];
110 132         406 scalar @parts;
111 18         82 };
112              
113             my $num_args = sub {
114 134     134   567 my $action = $_[0];
115 134         139 my $num = -1;
116 134 100       529 if (defined $action->attributes->{CaptureArgs}[0]) {
    100          
117 12         29 $num = $action->attributes->{CaptureArgs}[0];
118             }
119             elsif (defined $action->num_args) {
120 97         165 $num = $action->num_args;
121             }
122              
123 134         296 $num;
124 18         70 };
125              
126 18         89 $self->actions->{ '/' . $action->reverse } = $action;
127 18 100       55 push @{ $self->endpoints }, $action unless $action->attributes->{CaptureArgs};
  14         42  
128              
129 18         83 @$children = sort { $num_parts->($b) <=> $num_parts->($a) } sort { $num_args->($b) <=> $num_args->($a) } @$children, $action;
  66         103  
  67         119  
130             }
131              
132             sub used {
133 1     1 0 2 my ($self) = @_;
134 1         2 scalar @{ $self->endpoints };
  1         7  
135             }
136              
137             sub list {
138 0     0 0   my ($self) = @_;
139 0 0         return unless $self->used;
140              
141 0           my @rows = [[ 1 => 'Path Spec'], [ 1 => 'Private' ]];
142 0           my @unattached;
143              
144 0           for my $endpoint (sort { $a->reverse cmp $b->reverse } @{ $self->endpoints }) {
  0            
  0            
145 0 0         my @parts = defined $endpoint->num_args
146             ? ( ('*') x $endpoint->num_args )
147             : ('...');
148 0           my @parents;
149              
150 0           my $cur = $endpoint;
151 0           my $parent;
152 0           while ($cur) {
153 0 0         if (my $cap = $cur->attributes->{CaptureArgs}) {
154 0 0         unshift @parts, (('*') x $cap->[0]) if $cap->[0];
155             }
156 0 0         if (my $pp = $cur->attributes->{PathPart}) {
157 0 0 0       unshift @parts, $pp->[0]
158             if defined $pp->[0] and length $pp->[0];
159             }
160 0           $parent = $cur->attributes->{Chained}[0];
161 0           $cur = $self->actions->{ $parent };
162              
163 0 0         unshift @parents, $cur if $cur;
164             }
165              
166 0 0         if ($parent ne '/') {
167 0   0       push @unattached,
168             [ '/' . ($parents[0] || $endpoint)->reverse, $parent ];
169 0           next;
170             }
171              
172 0           my @r;
173 0           for my $parent (@parents) {
174 0 0         my $name = $parent->reverse eq $parents[0]->reverse
175             ? '/' . $parent->reverse
176             : '-> ' . $parent->reverse;
177              
178 0 0         if (my $cap = $parent->attributes->{CaptureArgs}) {
179 0           $name .= ' (' . $cap->[0] . ')';
180             }
181              
182 0           push @r, [ '', $name ];
183             }
184 0 0         push @r, [ '', (@r ? '=> ' : '') . '/' . $endpoint->reverse ];
185 0   0       $r[0][0] = join('/', '', @parts) || '/';
186              
187 0           push @rows, @r;
188             }
189              
190 0 0         if (@unattached) {
191 0           push @rows, undef;
192 0           push @rows, ['Private', 'Missing parent'];
193 0           push @rows, undef;
194              
195 0           push @rows, @unattached;
196             }
197              
198 0           \@rows;
199             }
200              
201             __PACKAGE__->meta->make_immutable;