DispatchTypes' POD refers to Catalyst::Manual::Intro, nominated as the best location...
[catagits/Catalyst-Runtime.git] / lib / Catalyst / DispatchType / Regex.pm
1 package Catalyst::DispatchType::Regex;
2
3 use Moose;
4 extends 'Catalyst::DispatchType::Path';
5
6 use Text::SimpleTable;
7 use Catalyst::Utils;
8 use Text::Balanced ();
9
10 has _compiled => (
11                   is => 'rw',
12                   isa => 'ArrayRef',
13                   required => 1,
14                   default => sub{ [] },
15                  );
16
17 no Moose;
18
19 =head1 NAME
20
21 Catalyst::DispatchType::Regex - Regex DispatchType
22
23 =head1 SYNOPSIS
24
25 See L<Catalyst::DispatchType>.
26
27 =head1 DESCRIPTION
28
29 Dispatch type managing path-matching behaviour using regexes.  For
30 more information on dispatch types, see:
31
32 =over 4
33
34 =item * L<Catalyst::Manual::Intro> for how they affect website authors
35
36 =item * L<Catalyst::DispatchType> for implementation information.
37
38 =back
39
40 =head1 METHODS
41
42 =head2 $self->list($c)
43
44 Output a table of all regex actions, and their private equivalent.
45
46 =cut
47
48 sub list {
49     my ( $self, $c ) = @_;
50     my $column_width = Catalyst::Utils::term_width() - 35 - 9;
51     my $re = Text::SimpleTable->new( [ 35, 'Regex' ], [ $column_width, 'Private' ] );
52     for my $regex ( @{ $self->_compiled } ) {
53         my $action = $regex->{action};
54         $re->row( $regex->{path}, "/$action" );
55     }
56     $c->log->debug( "Loaded Regex actions:\n" . $re->draw . "\n" )
57       if ( @{ $self->_compiled } );
58 }
59
60 =head2 $self->match( $c, $path )
61
62 Checks path against every compiled regex, and offers the action for any regex
63 which matches a chance to match the request. If it succeeds, sets action,
64 match and captures on $c->req and returns 1. If not, returns 0 without
65 altering $c.
66
67 =cut
68
69 sub match {
70     my ( $self, $c, $path ) = @_;
71
72     return if $self->SUPER::match( $c, $path );
73
74     # Check path against plain text first
75
76     foreach my $compiled ( @{ $self->_compiled } ) {
77         if ( my @captures = ( $path =~ $compiled->{re} ) ) {
78             next unless $compiled->{action}->match($c);
79             $c->req->action( $compiled->{path} );
80             $c->req->match($path);
81             $c->req->captures( \@captures );
82             $c->action( $compiled->{action} );
83             $c->namespace( $compiled->{action}->namespace );
84             return 1;
85         }
86     }
87
88     return 0;
89 }
90
91 =head2 $self->register( $c, $action )
92
93 Registers one or more regex actions for an action object.
94 Also registers them as literal paths.
95
96 Returns 1 if any regexps were registered.
97
98 =cut
99
100 sub register {
101     my ( $self, $c, $action ) = @_;
102     my $attrs    = $action->attributes;
103     my @register = @{ $attrs->{'Regex'} || [] };
104
105     foreach my $r (@register) {
106         $self->register_path( $c, $r, $action );
107         $self->register_regex( $c, $r, $action );
108     }
109
110     return 1 if @register;
111     return 0;
112 }
113
114 =head2 $self->register_regex($c, $re, $action)
115
116 Register an individual regex on the action. Usually called from the
117 register method.
118
119 =cut
120
121 sub register_regex {
122     my ( $self, $c, $re, $action ) = @_;
123     push(
124         @{ $self->_compiled },    # and compiled regex for us
125         {
126             re     => qr#$re#,
127             action => $action,
128             path   => $re,
129         }
130     );
131 }
132
133 =head2 $self->uri_for_action($action, $captures)
134
135 returns a URI for this action if it can find a regex attributes that contains
136 the correct number of () captures. Note that this may function incorrectly
137 in the case of nested captures - if your regex does (...(..))..(..) you'll
138 need to pass the first and third captures only.
139
140 =cut
141
142 sub uri_for_action {
143     my ( $self, $action, $captures ) = @_;
144
145     if (my $regexes = $action->attributes->{Regex}) {
146         REGEX: foreach my $orig (@$regexes) {
147             my $re = "$orig";
148             $re =~ s/^\^//;
149             $re =~ s/\$$//;
150             my $final = '/';
151             my @captures = @$captures;
152             while (my ($front, $rest) = split(/\(/, $re, 2)) {
153                 ($rest, $re) =
154                     Text::Balanced::extract_bracketed("(${rest}", '(');
155                 next REGEX unless @captures;
156                 $final .= $front.shift(@captures);
157             }
158             next REGEX if @captures;
159             return $final;
160          }
161     }
162     return undef;
163 }
164
165 =head1 AUTHORS
166
167 Catalyst Contributors, see Catalyst.pm
168
169 =head1 COPYRIGHT
170
171 This program is free software, you can redistribute it and/or modify it under
172 the same terms as Perl itself.
173
174 =cut
175
176 __PACKAGE__->meta->make_immutable;
177
178 1;