| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Catalyst::ActionRole::Methods; |
|
2
|
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
2737901
|
use Moose::Role; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $VERSION = '0.104'; |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
around 'list_extra_info' => sub { |
|
8
|
|
|
|
|
|
|
my $orig = shift; |
|
9
|
|
|
|
|
|
|
my $self = shift; |
|
10
|
|
|
|
|
|
|
my $info = $self->$orig( @_ ); |
|
11
|
|
|
|
|
|
|
$info->{'HTTP_METHOD'} = [ $self->get_allowed_methods( $self->class, undef, $self->name ) ]; |
|
12
|
|
|
|
|
|
|
$info; |
|
13
|
|
|
|
|
|
|
}; |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
around 'dispatch', sub { |
|
16
|
|
|
|
|
|
|
my $orig = shift; |
|
17
|
|
|
|
|
|
|
my $self = shift; |
|
18
|
|
|
|
|
|
|
my $c = shift; |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
my $return = $self->$orig($c, @_); |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
my $class = $self->class; |
|
23
|
|
|
|
|
|
|
my $controller = $c->component( $class ); |
|
24
|
|
|
|
|
|
|
my $method_name = $self->name; |
|
25
|
|
|
|
|
|
|
my $req_method = $c->request->method; |
|
26
|
|
|
|
|
|
|
my $suffix = uc $req_method; |
|
27
|
|
|
|
|
|
|
my ( $rest_method, $code ); |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
{ |
|
30
|
|
|
|
|
|
|
$rest_method = $method_name . '_' . $suffix; |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
if ( $code = $controller->action_for( $rest_method ) ) { |
|
33
|
|
|
|
|
|
|
my $sub_return = $c->forward( $code, $c->request->args ); |
|
34
|
|
|
|
|
|
|
return defined $sub_return ? $sub_return : $return; |
|
35
|
|
|
|
|
|
|
} elsif ( $code = $controller->can( $rest_method ) ) { |
|
36
|
|
|
|
|
|
|
# nothing to do |
|
37
|
|
|
|
|
|
|
} elsif ( 'OPTIONS' eq $suffix ) { |
|
38
|
|
|
|
|
|
|
$c->response->status( 204 ); |
|
39
|
|
|
|
|
|
|
} elsif ( 'HEAD' eq $suffix ) { |
|
40
|
|
|
|
|
|
|
$suffix = 'GET'; |
|
41
|
|
|
|
|
|
|
redo; |
|
42
|
|
|
|
|
|
|
} elsif ( 'not_implemented' eq $suffix ) { |
|
43
|
|
|
|
|
|
|
( my $enc_req_method = $req_method ) =~ s[(["'&<>])]{ '&#'.(ord $1).';' }ge; |
|
44
|
|
|
|
|
|
|
$c->response->status( 405 ); |
|
45
|
|
|
|
|
|
|
$c->response->content_type( 'text/html' ); |
|
46
|
|
|
|
|
|
|
$c->response->body( |
|
47
|
|
|
|
|
|
|
'<!DOCTYPE html><title>405 Method Not Allowed</title>' |
|
48
|
|
|
|
|
|
|
. "<p>The requested method $enc_req_method is not allowed for this URL.</p>" |
|
49
|
|
|
|
|
|
|
); |
|
50
|
|
|
|
|
|
|
} else { |
|
51
|
|
|
|
|
|
|
$suffix = 'not_implemented'; |
|
52
|
|
|
|
|
|
|
redo; |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
} |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
if ( not $code ) { |
|
57
|
|
|
|
|
|
|
my @allowed = $self->get_allowed_methods( $class, $c, $method_name ); |
|
58
|
|
|
|
|
|
|
$c->response->header( Allow => @allowed ? \@allowed : '' ); |
|
59
|
|
|
|
|
|
|
} |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# localise stuff so we can dispatch the action 'as normal, but get |
|
62
|
|
|
|
|
|
|
# different stats shown, and different code run. |
|
63
|
|
|
|
|
|
|
# Also get the full path for the action, and make it look like a forward |
|
64
|
|
|
|
|
|
|
local $self->{'code'} = $code || sub {}; |
|
65
|
|
|
|
|
|
|
( local $self->{'reverse'} = "-> $self->{'reverse'}" ) =~ s{[^/]+\z}{$rest_method}; |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
my $sub_return = $c->execute( $class, $self, @{ $c->request->args } ); |
|
68
|
|
|
|
|
|
|
defined $sub_return ? $sub_return : $return; |
|
69
|
|
|
|
|
|
|
}; |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
sub get_allowed_methods { |
|
72
|
2
|
|
|
2
|
1
|
24
|
my ( $self, $controller, $c, $name ) = @_; |
|
73
|
2
|
|
33
|
|
|
13
|
my $class = ref $controller || $controller; # backcompat |
|
74
|
2
|
|
|
|
|
53
|
my %methods = map /^\Q$name\E\_(.+)()$/, $class->meta->get_all_method_names; |
|
75
|
2
|
100
|
|
|
|
5234
|
$methods{'HEAD'} = 1 if exists $methods{'GET'}; |
|
76
|
2
|
|
|
|
|
4
|
delete $methods{'not_implemented'}; |
|
77
|
2
|
|
|
|
|
14
|
sort keys %methods; |
|
78
|
|
|
|
|
|
|
} |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
1; |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
__END__ |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=pod |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=encoding UTF-8 |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=head1 NAME |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
Catalyst::ActionRole::Methods - Dispatch by HTTP Methods |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
sub foo : Local Does('Methods') { |
|
95
|
|
|
|
|
|
|
my ($self, $c, $arg) = @_; |
|
96
|
|
|
|
|
|
|
# called first, regardless of HTTP request method |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
sub foo_GET : Action { |
|
100
|
|
|
|
|
|
|
my ($self, $c, $arg) = @_; |
|
101
|
|
|
|
|
|
|
# called next, but only for GET requests |
|
102
|
|
|
|
|
|
|
# this is passed the same @_ as its generic action |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub foo_POST { # does not need to be an action |
|
106
|
|
|
|
|
|
|
my ($self, $c, $arg) = @_; |
|
107
|
|
|
|
|
|
|
# likewise for POST requests |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
sub foo_not_implemented { # fallback |
|
111
|
|
|
|
|
|
|
my ($self, $c, $arg) = @_; |
|
112
|
|
|
|
|
|
|
# only needed if you want to override the default 405 response |
|
113
|
|
|
|
|
|
|
} |
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
This is a L<Catalyst> extension which adds additional dispatch based on the |
|
118
|
|
|
|
|
|
|
HTTP method, in the same way L<Catalyst::Action::REST> does: |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
An action which does this role will be matched and run as usual. But after it |
|
121
|
|
|
|
|
|
|
returns, a sub-action will also run, which will be identified by taking the |
|
122
|
|
|
|
|
|
|
name of the main action and appending an underscore and the HTTP request method |
|
123
|
|
|
|
|
|
|
name. This sub-action is passed the same captures and args as the main action. |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
You can also write the sub-action as a plain method without declaring it as an |
|
126
|
|
|
|
|
|
|
action. Probably the only advantage of declaring it as an action is that other |
|
127
|
|
|
|
|
|
|
action roles can then be applied to it. |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
There are several fallbacks if a sub-action for the current request method does |
|
130
|
|
|
|
|
|
|
not exist: |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=over 3 |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=item 1. |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
C<HEAD> requests will try to use the sub-action for C<GET>. |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=item 2. |
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
C<OPTIONS> requests will set up a 204 (No Content) response. |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=item 3. |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
The C<not_implemented> sub-action is tried as a last resort. |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=item 4. |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
Finally, a 405 (Method Not Found) response is set up. |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=back |
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
Both fallback responses include an C<Allow> header which will be populated from |
|
153
|
|
|
|
|
|
|
the available sub-actions. |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Note that this action role only I<adds> dispatch. It does not affect matching! |
|
156
|
|
|
|
|
|
|
The main action will always run if it otherwise matches the request, even if no |
|
157
|
|
|
|
|
|
|
suitable sub-action exists and a 405 is generated. Nor does it affect chaining. |
|
158
|
|
|
|
|
|
|
All subsequent actions in a chain will still run, along with their sub-actions. |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=head1 INTERACTION WITH CHAINED DISPATCH |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
The fact that this is an action role which is attached to individual actions |
|
163
|
|
|
|
|
|
|
has some odd and unintuitive consequences when combining it with Chained |
|
164
|
|
|
|
|
|
|
dispatch, particularly when it is used in multiple actions in the same chain. |
|
165
|
|
|
|
|
|
|
This example will not work well at all: |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub foo : Chained(/) CaptureArgs(1) Does('Methods') { ... } |
|
168
|
|
|
|
|
|
|
sub foo_GET { ... } |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
sub bar : Chained(foo) Args(0) { ... } |
|
171
|
|
|
|
|
|
|
sub bar_POST { ... } |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
Because each action does its own isolated C<Methods> sub-dispatch, a C<GET> |
|
174
|
|
|
|
|
|
|
request to this chain will run C<foo>, then C<foo_GET>, then C<bar>, then |
|
175
|
|
|
|
|
|
|
set up a 405 response due to the absence of C<bar_GET>. And because C<bar> only |
|
176
|
|
|
|
|
|
|
has a sub-action for C<POST>, that is all the C<Allow> header will contain. |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
Worse (maybe), a C<POST> will run C<foo>, then set up a 405 response with an |
|
179
|
|
|
|
|
|
|
C<Allow> list of just C<GET>, but then still run C<bar> and C<bar_POST>. |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
This means it is never useful for an action which is further along a chain to |
|
182
|
|
|
|
|
|
|
have I<more> sub-actions than any earlier action. |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
Having I<fewer> sub-actions can be useful: if the earlier part of the chain is |
|
185
|
|
|
|
|
|
|
shared with other chains then each chain can handle a different set of request |
|
186
|
|
|
|
|
|
|
methods: |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
sub foo : Chained(/) CaptureArgs(1) Does('Methods') { ... } |
|
189
|
|
|
|
|
|
|
sub foo_GET { ... } |
|
190
|
|
|
|
|
|
|
sub foo_POST { ... } |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub bar : Chained(foo) Args(0) { ... } |
|
193
|
|
|
|
|
|
|
sub bar_GET { ... } |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub quux : Chained(foo) Args(0) { ... } |
|
196
|
|
|
|
|
|
|
sub quux_POST { ... } |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
In this example, the C</foo/bar> chain will handle only C<GET> while the |
|
199
|
|
|
|
|
|
|
C</foo/quux> chain will handle only C<POST>. If you later wanted to make |
|
200
|
|
|
|
|
|
|
C</foo/quux> also handle C<GET> then you would only need to add C<quux_GET> |
|
201
|
|
|
|
|
|
|
because there is already a C<foo_GET>. But to make C</foo/bar> handle C<PUT>, |
|
202
|
|
|
|
|
|
|
you would need to add both C<foo_PUT> I<and> C<bar_PUT>. |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=head1 VERSUS Catalyst::Action::REST |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
L<Catalyst::Action::REST> works fine doesn't it? Why offer a new approach? There's |
|
207
|
|
|
|
|
|
|
a few reasons: |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
First, when L<Catalyst::Action::REST> was written we did not have |
|
210
|
|
|
|
|
|
|
L<Moose> and the only way to augment functionality was via inheritance. Now that |
|
211
|
|
|
|
|
|
|
L<Moose> is common we instead say that it is typically better to use a L<Moose::Role> |
|
212
|
|
|
|
|
|
|
to augment a class function rather to use a subclass. The role approach is a smaller |
|
213
|
|
|
|
|
|
|
hammer and it plays nicer when you need to combine several roles to augment a class |
|
214
|
|
|
|
|
|
|
(as compared to multiple inheritance approaches.). This is why we brought support for |
|
215
|
|
|
|
|
|
|
action roles into core L<Catalyst::Controller> several years ago. Letting you have |
|
216
|
|
|
|
|
|
|
this functionality via a role should lead to more flexible systems that play nice |
|
217
|
|
|
|
|
|
|
with other roles. One nice side effect of this 'play nice with others' is that we |
|
218
|
|
|
|
|
|
|
were able to hook into the 'list_extra_info' method of the core action class so that |
|
219
|
|
|
|
|
|
|
you can now see in your developer mode debug output the matched http methods, for |
|
220
|
|
|
|
|
|
|
example: |
|
221
|
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
.-------------------------------------+----------------------------------------. |
|
223
|
|
|
|
|
|
|
| Path Spec | Private | |
|
224
|
|
|
|
|
|
|
+-------------------------------------+----------------------------------------+ |
|
225
|
|
|
|
|
|
|
| /myaction/*/next_action_in_chain | GET, HEAD, POST /myaction (1) | |
|
226
|
|
|
|
|
|
|
| | => /next_action_in_chain (0) | |
|
227
|
|
|
|
|
|
|
'-------------------------------------+----------------------------------------' |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
This is not to say its never correct to use an action class, but now you have the |
|
230
|
|
|
|
|
|
|
choice. |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
Second, L<Catalyst::Action::REST> has the behavior as noted of altering the core |
|
233
|
|
|
|
|
|
|
L<Catalyst::Request> class. This might not be desired and has always struck the |
|
234
|
|
|
|
|
|
|
author as a bit too much side effect / action at a distance. |
|
235
|
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
Last, L<Catalyst::Action::REST> is actually a larger distribution with a bunch of |
|
237
|
|
|
|
|
|
|
other features and dependencies that you might not want. The intention is to offer |
|
238
|
|
|
|
|
|
|
those bits of functionality as standalone, modern components and allow one to assemble |
|
239
|
|
|
|
|
|
|
the parts needed, as needed. |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
This action role is for the most part a 1-1 port of the action class, with one minor |
|
242
|
|
|
|
|
|
|
change to reduce the dependency count. Additionally, it does not automatically |
|
243
|
|
|
|
|
|
|
apply the L<Catalyst::Request::REST> action class to your global L<Catalyst> |
|
244
|
|
|
|
|
|
|
action class. This feature is left off because its easy to set this yourself if |
|
245
|
|
|
|
|
|
|
desired via the global L<Catalyst> configuration and we want to follow and promote |
|
246
|
|
|
|
|
|
|
the idea of 'do one thing well and nothing surprising'. |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
B<NOTE> There is an additional minor change in how we handle return values from actions. In |
|
249
|
|
|
|
|
|
|
general L<Catalyst> does nothing with an action return value (unless in an auto action). |
|
250
|
|
|
|
|
|
|
However this might not always be the future case, and you might have used that return value |
|
251
|
|
|
|
|
|
|
for something in your custom code. In L<Catalyst::Action::REST> the return value was |
|
252
|
|
|
|
|
|
|
always the return of the dispatched sub action (if any). We tweaked this so that we use |
|
253
|
|
|
|
|
|
|
the sub action return value, BUT if that value is undefined, we use the parent action |
|
254
|
|
|
|
|
|
|
return value instead. |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
We also dropped saying 'REST' when all we are doing is dispatching on HTTP method. |
|
257
|
|
|
|
|
|
|
Since the time that the first version of L<Catalysts::Action::REST> was released to |
|
258
|
|
|
|
|
|
|
CPAN our notion of what 'REST' means has greatly evolved so I think its correct to |
|
259
|
|
|
|
|
|
|
change the name to be functionality specific and to not confuse people that are new |
|
260
|
|
|
|
|
|
|
to the REST discipline. |
|
261
|
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
This action role is intended to be used in all the places |
|
263
|
|
|
|
|
|
|
you used to use the action class and have the same results, with the exception |
|
264
|
|
|
|
|
|
|
of the already mentioned 'not messing with the global request class'. However |
|
265
|
|
|
|
|
|
|
L<Catalyst::Action::REST> has been around for a long time and is well vetted in |
|
266
|
|
|
|
|
|
|
production so I would caution care with changing your mission critical systems |
|
267
|
|
|
|
|
|
|
very quickly. |
|
268
|
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=head1 VERSUS NATIVE METHOD ATTRIBUTES |
|
270
|
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
L<Catalyst> since version 5.90030 has offered a core approach to dispatch on the |
|
272
|
|
|
|
|
|
|
http method (via L<Catalyst::ActionRole::HTTPMethods>). Why still use this action role |
|
273
|
|
|
|
|
|
|
versus the core functionality? ALthough it partly comes down to preference and the |
|
274
|
|
|
|
|
|
|
author's desire to give current users of L<Catalyst::Action::REST> a path forward, there |
|
275
|
|
|
|
|
|
|
is some functionality differences beetween the two which may recommend one over the |
|
276
|
|
|
|
|
|
|
other. For example the core method matching does not offer an automatic default |
|
277
|
|
|
|
|
|
|
'Not Implemented' response that correctly sets the OPTIONS header. Also the dispatch |
|
278
|
|
|
|
|
|
|
flow between the two approaches is different and when using chained actions one |
|
279
|
|
|
|
|
|
|
might be a better choice over the other depending on how your chains are arranged and |
|
280
|
|
|
|
|
|
|
your desired flow of action. |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=head1 METHODS |
|
283
|
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
This role contains the following methods. |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
=head2 get_allowed_methods |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
Returns a list of the allowed methods. |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=head2 dispatch |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
This method overrides the default dispatch mechanism to the re-dispatching |
|
293
|
|
|
|
|
|
|
mechanism described above. |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=head1 CONTRIBUTORS |
|
296
|
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
This module is based on code, tests and documentation extracted out of |
|
298
|
|
|
|
|
|
|
L<Catalyst::Action::REST>, which was originally developed by Adam Jacob |
|
299
|
|
|
|
|
|
|
with lots of help from mst and jrockway, while being paid by Marchex, Inc |
|
300
|
|
|
|
|
|
|
(http://www.marchex.com). |
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
The following people also contributed to parts copied from that package: |
|
303
|
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
Tomas Doran (t0m) E<lt>bobtfish@bobtfish.netE<gt> |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
Dave Rolsky E<lt>autarch@urth.orgE<gt> |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
Arthur Axel "fREW" Schmidt E<lt>frioux@gmail.comE<gt> |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
J. Shirley E<lt>jshirley@gmail.comE<gt> |
|
311
|
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
Wallace Reis E<lt>wreis@cpan.orgE<gt> |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
=head1 AUTHOR |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
Aristotle Pagaltzis <pagaltzis@gmx.de> |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
John Napiorkowski <jjnapiork@cpan.org> |
|
319
|
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
321
|
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
This software is copyright (c) 2024 by Aristotle Pagaltzis. |
|
323
|
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
|
325
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
=cut |