| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package CGI::Easy::URLconf; |
|
2
|
|
|
|
|
|
|
|
|
3
|
4
|
|
|
4
|
|
98834
|
use warnings; |
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
126
|
|
|
4
|
4
|
|
|
4
|
|
22
|
use strict; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
121
|
|
|
5
|
4
|
|
|
4
|
|
22
|
use Carp; |
|
|
4
|
|
|
|
|
13
|
|
|
|
4
|
|
|
|
|
359
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
4
|
|
|
4
|
|
3633
|
use version; our $VERSION = qv('1.0.1'); # REMINDER: update Changes |
|
|
4
|
|
|
|
|
8942
|
|
|
|
4
|
|
|
|
|
33
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
# REMINDER: update dependencies in Makefile.PL |
|
10
|
4
|
|
|
4
|
|
4173
|
use Perl6::Export::Attrs; |
|
|
4
|
|
|
|
|
33779
|
|
|
|
4
|
|
|
|
|
31
|
|
|
11
|
4
|
|
|
4
|
|
4143
|
use URI::Escape qw( uri_escape_utf8 ); |
|
|
4
|
|
|
|
|
6503
|
|
|
|
4
|
|
|
|
|
1491
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
my %PATH2VIEW; |
|
15
|
|
|
|
|
|
|
my %VIEW2PATH; |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
sub setup_path :Export { |
|
19
|
6
|
|
|
6
|
1
|
1345
|
my (@data) = @_; |
|
20
|
6
|
100
|
100
|
|
|
51
|
my $method = ref $data[0] || $data[0] =~ m{\A/}xms ? q{} : shift @data; |
|
21
|
6
|
|
|
|
|
23
|
for (my $i = 0; $i <= $#data; $i++) { |
|
22
|
13
|
|
|
|
|
21
|
my $match = $data[$i]; |
|
23
|
13
|
100
|
|
|
|
28
|
if (ref $match) { |
|
24
|
5
|
50
|
|
|
|
21
|
croak "expect SCALAR or Regexp at parameter $i" if ref $match ne 'Regexp'; |
|
25
|
|
|
|
|
|
|
} else { |
|
26
|
8
|
50
|
|
|
|
33
|
croak "path at parameter $i must begin with /" if $match !~ m{\A/}xms; |
|
27
|
|
|
|
|
|
|
} |
|
28
|
13
|
50
|
|
|
|
34
|
croak 'not enough params' if $i == $#data; |
|
29
|
13
|
|
|
|
|
26
|
my @code = ($data[++$i]); |
|
30
|
13
|
50
|
|
|
|
32
|
croak "expect CODE at parameter $i" if ref $code[0] ne 'CODE'; |
|
31
|
13
|
|
|
|
|
55
|
while (ref $data[$i+1] eq 'CODE') { |
|
32
|
3
|
|
|
|
|
70
|
push @code, $data[++$i]; |
|
33
|
|
|
|
|
|
|
} |
|
34
|
13
|
|
|
|
|
18
|
my $view = pop @code; |
|
35
|
13
|
|
|
|
|
15
|
push @{ $PATH2VIEW{$method} }, { |
|
|
13
|
|
|
|
|
93
|
|
|
36
|
|
|
|
|
|
|
match => $match, |
|
37
|
|
|
|
|
|
|
view => $view, |
|
38
|
|
|
|
|
|
|
prepare => \@code, |
|
39
|
|
|
|
|
|
|
}; |
|
40
|
|
|
|
|
|
|
} |
|
41
|
6
|
|
|
|
|
19
|
return; |
|
42
|
4
|
|
|
4
|
|
31
|
} |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
49
|
|
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub path2view :Export { |
|
45
|
17
|
|
|
17
|
1
|
1761
|
my ($r) = @_; |
|
46
|
17
|
|
|
|
|
23
|
my $path = $r->{path}; |
|
47
|
17
|
|
|
|
|
18
|
my $view; |
|
48
|
17
|
|
100
|
|
|
86
|
my $for_method = $PATH2VIEW{ $r->{ENV}{REQUEST_METHOD} } || []; |
|
49
|
17
|
|
50
|
|
|
41
|
my $for_any = $PATH2VIEW{ q{} } || []; |
|
50
|
17
|
|
|
|
|
16
|
for my $path2view (@{$for_method}, @{$for_any}) { |
|
|
17
|
|
|
|
|
31
|
|
|
|
17
|
|
|
|
|
33
|
|
|
51
|
63
|
|
|
|
|
56
|
my @match; |
|
52
|
63
|
|
|
|
|
79
|
my $match = $path2view->{match}; |
|
53
|
63
|
100
|
|
|
|
96
|
if (!ref $match) { |
|
54
|
45
|
100
|
|
|
|
120
|
next if $path ne $match; |
|
55
|
|
|
|
|
|
|
} else { |
|
56
|
18
|
100
|
|
|
|
98
|
next if $path !~ /$match/xms; |
|
57
|
5
|
|
|
|
|
22
|
for my $i (0 .. $#-) { |
|
58
|
9
|
50
|
|
|
|
27
|
if (defined $-[$i]) { |
|
59
|
9
|
|
|
|
|
50
|
push @match, substr $path, $-[$i], $+[$i] - $-[$i]; |
|
60
|
|
|
|
|
|
|
} |
|
61
|
|
|
|
|
|
|
else { |
|
62
|
0
|
|
|
|
|
0
|
push @match, undef; |
|
63
|
|
|
|
|
|
|
} |
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
} |
|
66
|
13
|
|
|
|
|
17
|
for my $prepare (@{ $path2view->{prepare} }) { |
|
|
13
|
|
|
|
|
25
|
|
|
67
|
3
|
|
|
|
|
11
|
$prepare->($r, \@match); |
|
68
|
|
|
|
|
|
|
} |
|
69
|
13
|
|
|
|
|
80
|
return $path2view->{view}; |
|
70
|
|
|
|
|
|
|
} |
|
71
|
4
|
|
|
|
|
25
|
return; |
|
72
|
4
|
|
|
4
|
|
2192
|
} |
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
21
|
|
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
sub set_param :Export { |
|
75
|
3
|
|
|
3
|
1
|
24
|
my (@names) = @_; |
|
76
|
|
|
|
|
|
|
return sub { |
|
77
|
3
|
|
|
3
|
|
8
|
my ($r, $values) = @_; |
|
78
|
3
|
|
|
|
|
9
|
for my $i (0 .. $#names) { |
|
79
|
4
|
50
|
|
|
|
13
|
if (defined $values->[$i+1]) { |
|
80
|
4
|
50
|
|
|
|
16
|
if (ref $r->{GET}{ $names[$i] }) { |
|
81
|
0
|
|
|
|
|
0
|
$r->{GET}{ $names[$i] } = [ $values->[$i+1] ]; |
|
82
|
|
|
|
|
|
|
} else { |
|
83
|
4
|
|
|
|
|
15
|
$r->{GET}{ $names[$i] } = $values->[$i+1]; |
|
84
|
|
|
|
|
|
|
} |
|
85
|
|
|
|
|
|
|
} |
|
86
|
|
|
|
|
|
|
else { |
|
87
|
0
|
|
|
|
|
0
|
delete $r->{GET}{ $names[$i] }; |
|
88
|
|
|
|
|
|
|
} |
|
89
|
|
|
|
|
|
|
} |
|
90
|
3
|
|
|
|
|
10
|
return; |
|
91
|
3
|
|
|
|
|
27
|
}; |
|
92
|
4
|
|
|
4
|
|
1444
|
} |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
19
|
|
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
### |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub setup_view :Export { |
|
97
|
3
|
|
|
3
|
1
|
12
|
my (@data) = @_; |
|
98
|
3
|
|
|
|
|
12
|
for (my $i = 0; $i <= $#data; $i++) { |
|
99
|
4
|
|
|
|
|
8
|
my $view = $data[$i]; |
|
100
|
4
|
50
|
|
|
|
26
|
croak "expect CODE at parameter $i" if ref $view ne 'CODE'; |
|
101
|
4
|
50
|
|
|
|
12
|
croak "already exists CODE at parameter $i" if exists $VIEW2PATH{$view}; |
|
102
|
4
|
50
|
|
|
|
37
|
croak 'not enough params' if $i == $#data; |
|
103
|
4
|
|
|
|
|
9
|
my $path = $data[++$i]; |
|
104
|
4
|
50
|
66
|
|
|
22
|
croak "expect SCALAR or ARRAY at parameter $i" if ref $path && ref $path ne 'ARRAY'; |
|
105
|
4
|
50
|
66
|
|
|
13
|
croak "expect even elements in parameter $i" if ref $path && @{$path} % 2; |
|
|
2
|
|
|
|
|
12
|
|
|
106
|
4
|
|
|
|
|
19
|
$VIEW2PATH{$view} = $path; |
|
107
|
|
|
|
|
|
|
} |
|
108
|
3
|
|
|
|
|
8
|
return; |
|
109
|
4
|
|
|
4
|
|
1722
|
} |
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
20
|
|
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
sub view2path :Export { |
|
112
|
13
|
|
|
13
|
1
|
4548
|
my ($view, %p) = @_; |
|
113
|
13
|
|
|
|
|
28
|
my $path = $VIEW2PATH{$view}; |
|
114
|
13
|
100
|
|
|
|
34
|
if (!defined $path) { |
|
115
|
4
|
|
|
|
|
11
|
my @path = grep { $_->{view} eq $view } map { @{$_} } values %PATH2VIEW; |
|
|
27
|
|
|
|
|
69
|
|
|
|
8
|
|
|
|
|
10
|
|
|
|
8
|
|
|
|
|
20
|
|
|
116
|
4
|
100
|
66
|
|
|
30
|
if (@path == 1 && !ref $path[0]{match}) { |
|
117
|
3
|
|
|
|
|
8
|
$path = $path[0]{match}; |
|
118
|
|
|
|
|
|
|
} |
|
119
|
|
|
|
|
|
|
} |
|
120
|
13
|
100
|
|
|
|
38
|
croak 'unknown CODE, use setup_view first' if !defined $path; |
|
121
|
12
|
100
|
|
|
|
28
|
if (ref $path) { |
|
122
|
7
|
|
|
|
|
8
|
my @try = @{$path}; |
|
|
7
|
|
|
|
|
19
|
|
|
123
|
7
|
|
|
|
|
12
|
$path = undef; |
|
124
|
7
|
|
|
|
|
20
|
while (@try) { |
|
125
|
10
|
|
|
|
|
17
|
my $try = shift @try; |
|
126
|
10
|
|
|
|
|
17
|
my $tmpl= shift @try; |
|
127
|
10
|
100
|
|
|
|
26
|
my $values = $try->(\%p) or next; |
|
128
|
5
|
50
|
|
|
|
10
|
if (@{$values} != ($tmpl =~ tr/?//)) { |
|
|
5
|
|
|
|
|
18
|
|
|
129
|
0
|
|
|
|
|
0
|
croak "incorrect values amount for template '$tmpl'"; |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
# WARNING apache не ÑазÑеÑÐ°ÐµÑ %2F в пÑÑи (nginx ÑазÑеÑаеÑ) |
|
132
|
5
|
|
|
|
|
10
|
$_ = uri_escape_utf8($_), s/%2F/\//g for @{$values}; ## no critic |
|
|
5
|
|
|
|
|
26
|
|
|
133
|
5
|
|
|
|
|
180
|
$tmpl =~ s/[?]/shift @{$values}/xmsge; |
|
|
7
|
|
|
|
|
10
|
|
|
|
7
|
|
|
|
|
18
|
|
|
134
|
5
|
|
|
|
|
11
|
$path = $tmpl; |
|
135
|
5
|
|
|
|
|
11
|
last; |
|
136
|
|
|
|
|
|
|
} |
|
137
|
7
|
100
|
|
|
|
59
|
croak 'these parameters do not match configured urls' if !defined $path; |
|
138
|
|
|
|
|
|
|
} |
|
139
|
10
|
|
|
|
|
14
|
my @params; |
|
140
|
10
|
|
|
|
|
27
|
for my $n (keys %p) { |
|
141
|
2
|
50
|
|
|
|
11
|
my @v = ref $p{$n} ? @{ $p{$n} } : $p{$n}; |
|
|
0
|
|
|
|
|
0
|
|
|
142
|
2
|
|
|
|
|
4
|
for my $v (@v) { |
|
143
|
2
|
|
|
|
|
6
|
push @params, uri_escape_utf8($n).q{=}.uri_escape_utf8($v); |
|
144
|
|
|
|
|
|
|
} |
|
145
|
|
|
|
|
|
|
} |
|
146
|
10
|
100
|
|
|
|
73
|
if (@params) { |
|
147
|
2
|
|
|
|
|
8
|
$path .= q{?} . join q{&}, @params; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
10
|
|
|
|
|
35
|
return $path; |
|
150
|
4
|
|
|
4
|
|
2772
|
} |
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
19
|
|
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
sub with_params :Export { |
|
153
|
3
|
|
|
3
|
1
|
7
|
my (@names) = @_; |
|
154
|
|
|
|
|
|
|
return sub { |
|
155
|
10
|
|
|
10
|
|
14
|
my ($p) = @_; |
|
156
|
10
|
|
|
|
|
12
|
my @values; |
|
157
|
10
|
|
|
|
|
17
|
for my $name (@names) { |
|
158
|
12
|
100
|
|
|
|
48
|
return if !defined $p->{ $name }; |
|
159
|
7
|
|
|
|
|
20
|
push @values, $p->{ $name }; |
|
160
|
|
|
|
|
|
|
} |
|
161
|
5
|
|
|
|
|
19
|
delete $p->{$_} for @names; ## no critic |
|
162
|
5
|
|
|
|
|
18
|
return \@values; |
|
163
|
3
|
|
|
|
|
24
|
}; |
|
164
|
4
|
|
|
4
|
|
1282
|
} |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
16
|
|
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
1; # Magic true value required at end of module |
|
168
|
|
|
|
|
|
|
__END__ |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=encoding utf8 |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head1 NAME |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
CGI::Easy::URLconf - map url path to handler sub and vice versa |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
178
|
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
use CGI::Easy::URLconf qw( setup_path path2view set_param ); |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
setup_path( |
|
182
|
|
|
|
|
|
|
'/about/' => \&myabout, |
|
183
|
|
|
|
|
|
|
'/terms.php' => \&terms, |
|
184
|
|
|
|
|
|
|
qr{\A /articles/ \z}xms => \&list_all_articles, |
|
185
|
|
|
|
|
|
|
); |
|
186
|
|
|
|
|
|
|
setup_path( |
|
187
|
|
|
|
|
|
|
qr{\A /articles/(\d+)/ \z}xms |
|
188
|
|
|
|
|
|
|
=> set_param('year') |
|
189
|
|
|
|
|
|
|
=> \&list_articles, |
|
190
|
|
|
|
|
|
|
qr{\A /articles/tag/(\w+)/(\d+)/ \z}xms |
|
191
|
|
|
|
|
|
|
=> set_param('tag','year') |
|
192
|
|
|
|
|
|
|
=> \&list_articles, |
|
193
|
|
|
|
|
|
|
); |
|
194
|
|
|
|
|
|
|
setup_path( POST => |
|
195
|
|
|
|
|
|
|
'/articles/' => \&add_article, |
|
196
|
|
|
|
|
|
|
); |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
my $r = CGI::Easy::Request->new(); |
|
199
|
|
|
|
|
|
|
my $handler = path2view($r); |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
use CGI::Easy::URLconf qw( setup_view view2path with_params ); |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
setup_view( |
|
205
|
|
|
|
|
|
|
\&list_all_articles => '/articles/', |
|
206
|
|
|
|
|
|
|
\&list_articles => [ |
|
207
|
|
|
|
|
|
|
with_params('tag','year') => '/articles/tag/?/?/', |
|
208
|
|
|
|
|
|
|
with_params('year') => '/articles/?/', |
|
209
|
|
|
|
|
|
|
], |
|
210
|
|
|
|
|
|
|
); |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
# set $url to '/about/' |
|
213
|
|
|
|
|
|
|
my $url = view2path( \&myabout ); |
|
214
|
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
# set $url to '/articles/' |
|
216
|
|
|
|
|
|
|
my $url = view2path( \&list_all_articles ); |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# set $url to '/articles/2010/?month=12' |
|
219
|
|
|
|
|
|
|
my $url = view2path( \&list_articles, year=>2010, month=>12 ); |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
This module provide support for clean, user-friendly URLs. This can be |
|
225
|
|
|
|
|
|
|
archived by configuring web server to run your CGI/FastCGI script for any |
|
226
|
|
|
|
|
|
|
url requested by user, and let you manually dispatch different urls to |
|
227
|
|
|
|
|
|
|
corresponding handlers (subroutines). Additionally, you can take some |
|
228
|
|
|
|
|
|
|
CGI parameters from url's path instead of usual GET parameters. |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
The idea is to set rules when CGI/FastCGI starts using: |
|
231
|
|
|
|
|
|
|
a) setup_path() - to map url's path to handler subroutine |
|
232
|
|
|
|
|
|
|
(also called "view") |
|
233
|
|
|
|
|
|
|
b) setup_view() - to map handler subroutine to url |
|
234
|
|
|
|
|
|
|
and then use: |
|
235
|
|
|
|
|
|
|
a) path2view() - to get handler subroutine matching current url's path |
|
236
|
|
|
|
|
|
|
b) view2path() - to get url matching some handler subroutine |
|
237
|
|
|
|
|
|
|
(for inserting into HTML templates or sending redirects). |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
Example: |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# -- while CGI/FastCGI initialization |
|
242
|
|
|
|
|
|
|
setup_path( |
|
243
|
|
|
|
|
|
|
'/articles/' => \&list_articles, |
|
244
|
|
|
|
|
|
|
'/articles.php' => \&list_articles, |
|
245
|
|
|
|
|
|
|
'/index.php' => \&show_home_page, |
|
246
|
|
|
|
|
|
|
); |
|
247
|
|
|
|
|
|
|
setup_path( POST => |
|
248
|
|
|
|
|
|
|
'/articles/' => \&add_new_article, |
|
249
|
|
|
|
|
|
|
); |
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# -- when beginning to handle new CGI/FastCGI request |
|
252
|
|
|
|
|
|
|
my $r = CGI::Easy::Request->new(); |
|
253
|
|
|
|
|
|
|
my $handler = path2view($r); |
|
254
|
|
|
|
|
|
|
# $handler now set to: |
|
255
|
|
|
|
|
|
|
# \&list_articles if url path /articles/ and request method is GET |
|
256
|
|
|
|
|
|
|
# \&add_new_article if url path /articles/ and request method is POST |
|
257
|
|
|
|
|
|
|
# \&list_articles if url path /articles.php (any request method) |
|
258
|
|
|
|
|
|
|
# \&show_home_page if url path /index.php (any request method) |
|
259
|
|
|
|
|
|
|
# undef (in all other cases) |
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# -- while CGI/FastCGI initialization |
|
262
|
|
|
|
|
|
|
setup_view( |
|
263
|
|
|
|
|
|
|
\&list_articles => '/articles/', |
|
264
|
|
|
|
|
|
|
# we don't have to configure mapping for \&show_home_page |
|
265
|
|
|
|
|
|
|
# and \&add_new_article because their mappings can be |
|
266
|
|
|
|
|
|
|
# unambiguously automatically detected from above setup_path() |
|
267
|
|
|
|
|
|
|
); |
|
268
|
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# -- when preparing reply (HTML escaping omitted for simplicity) |
|
270
|
|
|
|
|
|
|
printf '<a href="%s">Articles</a>', view2path(\&list_articles); |
|
271
|
|
|
|
|
|
|
printf '<form method=POST action="%s">', view2path(\&add_new_article); |
|
272
|
|
|
|
|
|
|
# -- or redirecting to another url |
|
273
|
|
|
|
|
|
|
my $h = CGI::Easy::Headers->new(); |
|
274
|
|
|
|
|
|
|
$h->redirect(view2path(\&show_home_page)); |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
These two parts (setup_path() with path2view() and setup_view() with view2path()) |
|
277
|
|
|
|
|
|
|
can be used independently - for example, you don't have to use |
|
278
|
|
|
|
|
|
|
setup_view() and view2path() if you prefer to hardcode urls in HTML templates |
|
279
|
|
|
|
|
|
|
instead of generating them dynamically. But using both parts will let you |
|
280
|
|
|
|
|
|
|
configure I<all> urls used in your application in single place, which make |
|
281
|
|
|
|
|
|
|
it easier to control and modify them. |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
In addition to simple constant path to handler and vice versa mapping you |
|
284
|
|
|
|
|
|
|
can also map any path matching regular expression and even copy some data |
|
285
|
|
|
|
|
|
|
from path to GET parameters. Example: |
|
286
|
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# make /article/123/ same as /index.php?id=123 |
|
288
|
|
|
|
|
|
|
# use same handler for any url beginning with /old/ |
|
289
|
|
|
|
|
|
|
setup_path( |
|
290
|
|
|
|
|
|
|
'/article.php' => \&show_article, |
|
291
|
|
|
|
|
|
|
qr{^/article/(\d+)/$} => set_param('id') => \&show_article, |
|
292
|
|
|
|
|
|
|
qr{^/old/} => \&unsupported, |
|
293
|
|
|
|
|
|
|
); |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
# generate urls like /article/123/ dynamically |
|
296
|
|
|
|
|
|
|
setup_view( |
|
297
|
|
|
|
|
|
|
\&show_article => [ |
|
298
|
|
|
|
|
|
|
with_params('id') => '/article/?/', |
|
299
|
|
|
|
|
|
|
], |
|
300
|
|
|
|
|
|
|
); |
|
301
|
|
|
|
|
|
|
$url = view2path(\&show_article, id=>123); |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=head1 INTERFACE |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
=over |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=item setup_path( [METHOD =>] MATCH => [CALLBACK => ...] HANDLER, ... ) |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
Configure mapping of url's path to handler subroutine (which will be used |
|
311
|
|
|
|
|
|
|
by path2view()). |
|
312
|
|
|
|
|
|
|
Can be called multiple times and will just B<add> new mapping rules on each call. |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
If optional METHOD parameter defined, then all mapping rules in this |
|
315
|
|
|
|
|
|
|
setup_path() call will be applied only for requests with that HTTP method. |
|
316
|
|
|
|
|
|
|
If METHOD doesn't used, then these rules will be applied to all HTTP methods. |
|
317
|
|
|
|
|
|
|
If some path match both rules defined for current HTTP method and rules |
|
318
|
|
|
|
|
|
|
defined for any HTTP methods, will be used rule defined for current HTTP |
|
319
|
|
|
|
|
|
|
method. |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
MATCH parameter should be either SCALAR (string equal to url path) or |
|
322
|
|
|
|
|
|
|
Regexp (which can match any part of url path). |
|
323
|
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
HANDLER is REF to your subroutine, which will be returned by path2view() |
|
325
|
|
|
|
|
|
|
when this rule will match current url. |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
Between MATCH and HANDLER any amount of optional CALLBACK subroutines can |
|
328
|
|
|
|
|
|
|
be used. These CALLBACKs will be called when MATCH rule matches current |
|
329
|
|
|
|
|
|
|
url with two parameters: CGI::Easy::Request object and ARRAYREF with |
|
330
|
|
|
|
|
|
|
contents of all capturing parentheses (when MATCH rule is Regexp with |
|
331
|
|
|
|
|
|
|
capturing parentheses). Usual task for such CALLBACKs is convert "hidden" |
|
332
|
|
|
|
|
|
|
CGI parameters included in url path into usual C<< $r->{GET} >> parameters. |
|
333
|
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
Return nothing. |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=item path2view( $r ) |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
Take CGI::Easy::Request object as parameter, and analyse this request |
|
340
|
|
|
|
|
|
|
according to rules defined previously using setup_path(). |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
Return: HANDLER if find rule which match current request, else undef(). |
|
343
|
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=item set_param( @names ) |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
Take names of C<< {GET} >> parameters which should be set using parts of |
|
348
|
|
|
|
|
|
|
url path selected by capturing parentheses in MATCH Regexp. |
|
349
|
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
Return CALLBACK subroutine suitable for using in setup_path(). |
|
351
|
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=item setup_view( HANDLER => PATH, ... ) |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
Configure mapping of handler subroutine to url path (which will be used by |
|
356
|
|
|
|
|
|
|
view2path()). |
|
357
|
|
|
|
|
|
|
Can be called multiple times and will just B<add> new mapping rules on each call. |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
HANDLER must be REF to user's subroutine used to handle requests on PATH. |
|
360
|
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
PATH can be either STRING or ARRAYREF. |
|
362
|
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
If PATH is ARRAYREF, then this array should consist of CALLBACK => |
|
364
|
|
|
|
|
|
|
TEMPLATE pairs. CALLBACK is subroutine which will be executed by |
|
365
|
|
|
|
|
|
|
view2path() with single parameter C<< \%params >>, and should return |
|
366
|
|
|
|
|
|
|
either FALSE if this CALLBACK unable to handle these %params, or ARRAYREF |
|
367
|
|
|
|
|
|
|
with values to substitute into path TEMPLATE. TEMPLATE is path STRING |
|
368
|
|
|
|
|
|
|
which may contain '?' symbols - these will be replaced by values returned |
|
369
|
|
|
|
|
|
|
in ARRAYREF by CALLBACK which successfully handle %params. |
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
Example: map \&handler to /first/ or /second/ with 50% probability |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
setup_view( |
|
374
|
|
|
|
|
|
|
\&handler => [ |
|
375
|
|
|
|
|
|
|
sub { return rand < 0.5 ? [] : undef } => '/first/', |
|
376
|
|
|
|
|
|
|
sub { return [] } => '/second/', |
|
377
|
|
|
|
|
|
|
], |
|
378
|
|
|
|
|
|
|
); |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
Example: map \&handler to random article with id 0-999 |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
setup_view( |
|
383
|
|
|
|
|
|
|
\&handler => [ |
|
384
|
|
|
|
|
|
|
sub { return [ int rand 1000 ] } => '/article/?/', |
|
385
|
|
|
|
|
|
|
], |
|
386
|
|
|
|
|
|
|
); |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
Return nothing. |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
=item view2path( HANDLER, %params ) |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
Take user handler subroutine and it parameters, and convert it to url |
|
394
|
|
|
|
|
|
|
according to rules defined previously using setup_view(). |
|
395
|
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
Example: |
|
397
|
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
setup_view( |
|
399
|
|
|
|
|
|
|
\&handler => 'index.php', |
|
400
|
|
|
|
|
|
|
); |
|
401
|
|
|
|
|
|
|
my $url = view2path(\&handler, a=>'some string', b=>[6,7]); |
|
402
|
|
|
|
|
|
|
# $url will be: 'index.php?a=some%20string&b=6&b=7' |
|
403
|
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
If simple mapping from STRING to HANDLER was defined using setup_path(), |
|
405
|
|
|
|
|
|
|
and this is only mapping to HANDLER defined, then it's not necessary to |
|
406
|
|
|
|
|
|
|
define reverse mapping using setup_view() - it will be defined |
|
407
|
|
|
|
|
|
|
automatically. |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
Example: |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
setup_path( |
|
412
|
|
|
|
|
|
|
'/articles/' => \&list_articles, |
|
413
|
|
|
|
|
|
|
); |
|
414
|
|
|
|
|
|
|
my $url = view2path(\&list_articles); |
|
415
|
|
|
|
|
|
|
# $url will be: '/articles/' |
|
416
|
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
Return: url. Throw exception if unable to make url. |
|
418
|
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=item with_params( @names ) |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
Take names of parameters which B<must> exists in %params given to |
|
423
|
|
|
|
|
|
|
view2path(). |
|
424
|
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
Return CALLBACK subroutine suitable for using in setup_view(). |
|
426
|
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
=back |
|
429
|
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
=head1 BUGS AND LIMITATIONS |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
No bugs have been reported. |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
=head1 SUPPORT |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
Please report any bugs or feature requests through the web interface at |
|
439
|
|
|
|
|
|
|
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CGI-Easy-URLconf>. |
|
440
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress |
|
441
|
|
|
|
|
|
|
on your bug as I make changes. |
|
442
|
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
You can also look for information at: |
|
444
|
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=over |
|
446
|
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=CGI-Easy-URLconf> |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
L<http://annocpan.org/dist/CGI-Easy-URLconf> |
|
454
|
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
=item * CPAN Ratings |
|
456
|
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
L<http://cpanratings.perl.org/d/CGI-Easy-URLconf> |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=item * Search CPAN |
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
L<http://search.cpan.org/dist/CGI-Easy-URLconf/> |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
=back |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=head1 AUTHOR |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
Alex Efros C<< <powerman-asdf@ya.ru> >> |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
Copyright 2009-2010 Alex Efros <powerman-asdf@ya.ru>. |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
This program is distributed under the MIT (X11) License: |
|
476
|
|
|
|
|
|
|
L<http://www.opensource.org/licenses/mit-license.php> |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person |
|
479
|
|
|
|
|
|
|
obtaining a copy of this software and associated documentation |
|
480
|
|
|
|
|
|
|
files (the "Software"), to deal in the Software without |
|
481
|
|
|
|
|
|
|
restriction, including without limitation the rights to use, |
|
482
|
|
|
|
|
|
|
copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
483
|
|
|
|
|
|
|
copies of the Software, and to permit persons to whom the |
|
484
|
|
|
|
|
|
|
Software is furnished to do so, subject to the following |
|
485
|
|
|
|
|
|
|
conditions: |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be |
|
488
|
|
|
|
|
|
|
included in all copies or substantial portions of the Software. |
|
489
|
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
491
|
|
|
|
|
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
|
492
|
|
|
|
|
|
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
493
|
|
|
|
|
|
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
|
494
|
|
|
|
|
|
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
|
495
|
|
|
|
|
|
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|
496
|
|
|
|
|
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
|
497
|
|
|
|
|
|
|
OTHER DEALINGS IN THE SOFTWARE. |
|
498
|
|
|
|
|
|
|
|