Commit | Line | Data |
8077080c |
1 | package Catalyst::View::TT; |
2 | |
3 | use strict; |
7d8aa5ec |
4 | use base qw/Catalyst::View/; |
8077080c |
5 | use Template; |
6 | use Template::Timer; |
7 | use NEXT; |
8 | |
d3f19ee8 |
9 | our $VERSION = '0.23'; |
8077080c |
10 | |
11 | __PACKAGE__->mk_accessors('template'); |
e2bbd784 |
12 | __PACKAGE__->mk_accessors('include_path'); |
8077080c |
13 | |
9d574573 |
14 | *paths = \&include_path; |
15 | |
8077080c |
16 | =head1 NAME |
17 | |
18 | Catalyst::View::TT - Template View Class |
19 | |
20 | =head1 SYNOPSIS |
21 | |
8cd017a8 |
22 | # use the helper to create View |
722fede4 |
23 | myapp_create.pl view TT TT |
8077080c |
24 | |
8cd017a8 |
25 | # configure in lib/MyApp.pm |
26 | |
ae035767 |
27 | MyApp->config( |
8cd017a8 |
28 | name => 'MyApp', |
7d8aa5ec |
29 | root => MyApp->path_to('root');, |
ae035767 |
30 | 'View::TT' => { |
8cd017a8 |
31 | # any TT configurations items go here |
32 | INCLUDE_PATH => [ |
93f25382 |
33 | MyApp->path_to( 'root', 'src' ), |
34 | MyApp->path_to( 'root', 'lib' ), |
8cd017a8 |
35 | ], |
07571b2f |
36 | PRE_PROCESS => 'config/main', |
37 | WRAPPER => 'site/wrapper', |
38 | TEMPLATE_EXTENSION => '.tt', |
8cd017a8 |
39 | |
40 | # two optional config items |
41 | CATALYST_VAR => 'Catalyst', |
42 | TIMER => 1, |
43 | }, |
ae035767 |
44 | ); |
8cd017a8 |
45 | |
46 | # render view from lib/MyApp.pm or lib/MyApp::C::SomeController.pm |
47 | |
48 | sub message : Global { |
94b3529a |
49 | my ( $self, $c ) = @_; |
50 | $c->stash->{template} = 'message.tt2'; |
51 | $c->stash->{message} = 'Hello World!'; |
8cd017a8 |
52 | $c->forward('MyApp::V::TT'); |
53 | } |
8077080c |
54 | |
8cd017a8 |
55 | # access variables from template |
56 | |
57 | The message is: [% message %]. |
58 | |
59 | # example when CATALYST_VAR is set to 'Catalyst' |
60 | Context is [% Catalyst %] |
61 | The base is [% Catalyst.req.base %] |
62 | The name is [% Catalyst.config.name %] |
63 | |
64 | # example when CATALYST_VAR isn't set |
65 | Context is [% c %] |
66 | The base is [% base %] |
67 | The name is [% name %] |
68 | |
69 | =head1 DESCRIPTION |
70 | |
71 | This is the Catalyst view class for the L<Template Toolkit|Template>. |
72 | Your application should defined a view class which is a subclass of |
73 | this module. The easiest way to achieve this is using the |
74 | F<myapp_create.pl> script (where F<myapp> should be replaced with |
75 | whatever your application is called). This script is created as part |
76 | of the Catalyst setup. |
77 | |
78 | $ script/myapp_create.pl view TT TT |
79 | |
80 | This creates a MyApp::V::TT.pm module in the F<lib> directory (again, |
81 | replacing C<MyApp> with the name of your application) which looks |
82 | something like this: |
83 | |
84 | package FooBar::V::TT; |
85 | |
86 | use strict; |
87 | use base 'Catalyst::View::TT'; |
8077080c |
88 | |
89 | __PACKAGE__->config->{DEBUG} = 'all'; |
90 | |
8cd017a8 |
91 | Now you can modify your action handlers in the main application and/or |
92 | controllers to forward to your view class. You might choose to do this |
93 | in the end() method, for example, to automatically forward all actions |
94 | to the TT view class. |
722fede4 |
95 | |
8cd017a8 |
96 | # In MyApp or MyApp::Controller::SomeController |
8077080c |
97 | |
8cd017a8 |
98 | sub end : Private { |
94b3529a |
99 | my( $self, $c ) = @_; |
8cd017a8 |
100 | $c->forward('MyApp::V::TT'); |
101 | } |
8077080c |
102 | |
8cd017a8 |
103 | =head2 CONFIGURATION |
4687ac0d |
104 | |
8cd017a8 |
105 | There are a three different ways to configure your view class. The |
106 | first way is to call the C<config()> method in the view subclass. This |
107 | happens when the module is first loaded. |
8077080c |
108 | |
8cd017a8 |
109 | package MyApp::V::TT; |
110 | |
111 | use strict; |
112 | use base 'Catalyst::View::TT'; |
722fede4 |
113 | |
8cd017a8 |
114 | MyApp::V::TT->config({ |
7d8aa5ec |
115 | INCLUDE_PATH => [ |
116 | MyApp->path_to( 'root', 'templates', 'lib' ), |
117 | MyApp->path_to( 'root', 'templates', 'src' ), |
118 | ], |
8cd017a8 |
119 | PRE_PROCESS => 'config/main', |
120 | WRAPPER => 'site/wrapper', |
121 | }); |
122 | |
123 | The second way is to define a C<new()> method in your view subclass. |
124 | This performs the configuration when the view object is created, |
125 | shortly after being loaded. Remember to delegate to the base class |
126 | C<new()> method (via C<$self-E<gt>NEXT::new()> in the example below) after |
127 | performing any configuration. |
128 | |
129 | sub new { |
130 | my $self = shift; |
131 | $self->config({ |
7d8aa5ec |
132 | INCLUDE_PATH => [ |
133 | MyApp->path_to( 'root', 'templates', 'lib' ), |
134 | MyApp->path_to( 'root', 'templates', 'src' ), |
135 | ], |
8cd017a8 |
136 | PRE_PROCESS => 'config/main', |
137 | WRAPPER => 'site/wrapper', |
138 | }); |
139 | return $self->NEXT::new(@_); |
140 | } |
141 | |
94b3529a |
142 | The final, and perhaps most direct way, is to define a class |
8cd017a8 |
143 | item in your main application configuration, again by calling the |
94b3529a |
144 | uniquitous C<config()> method. The items in the class hash are |
8cd017a8 |
145 | added to those already defined by the above two methods. This happens |
146 | in the base class new() method (which is one reason why you must |
147 | remember to call it via C<NEXT> if you redefine the C<new()> method in a |
148 | subclass). |
149 | |
150 | package MyApp; |
151 | |
152 | use strict; |
153 | use Catalyst; |
154 | |
8cd017a8 |
155 | MyApp->config({ |
156 | name => 'MyApp', |
7d8aa5ec |
157 | root => MyApp->path_to('root'), |
7d607671 |
158 | 'V::TT' => { |
7d8aa5ec |
159 | INCLUDE_PATH => [ |
160 | MyApp->path_to( 'root', 'templates', 'lib' ), |
161 | MyApp->path_to( 'root', 'templates', 'src' ), |
162 | ], |
8cd017a8 |
163 | PRE_PROCESS => 'config/main', |
164 | WRAPPER => 'site/wrapper', |
165 | }, |
166 | }); |
167 | |
168 | Note that any configuration items defined by one of the earlier |
169 | methods will be overwritten by items of the same name provided by the |
170 | latter methods. |
171 | |
e2bbd784 |
172 | =head2 DYNAMIC INCLUDE_PATH |
173 | |
f7dfca06 |
174 | Sometimes it is desirable to modify INCLUDE_PATH for your templates at run time. |
175 | |
176 | Additional paths can be added to the start of INCLUDE_PATH via the stash as |
177 | follows: |
178 | |
179 | $c->stash->{additional_template_paths} = |
180 | [$c->config->{root} . '/test_include_path']; |
181 | |
182 | If you need to add paths to the end of INCLUDE_PATH, there is also an |
183 | include_path() accessor available: |
184 | |
185 | push( @{ $c->view('TT')->include_path }, qw/path/ ); |
186 | |
187 | Note that if you use include_path() to add extra paths to INCLUDE_PATH, you |
188 | MUST check for duplicate paths. Without such checking, the above code will add |
189 | "path" to INCLUDE_PATH at every request, causing a memory leak. |
190 | |
191 | A safer approach is to use include_path() to overwrite the array of paths |
192 | rather than adding to it. This eliminates both the need to perform duplicate |
193 | checking and the chance of a memory leak: |
e2bbd784 |
194 | |
f7dfca06 |
195 | @{ $c->view('TT')->include_path } = qw/path another_path/; |
e2bbd784 |
196 | |
1bc9fc55 |
197 | If you are calling C<render> directly then you can specify dynamic paths by |
9d574573 |
198 | having a C<additional_template_paths> key with a value of additonal directories |
1bc9fc55 |
199 | to search. See L<CAPTURING TEMPLATE OUTPUT> for an example showing this. |
200 | |
8cd017a8 |
201 | =head2 RENDERING VIEWS |
202 | |
203 | The view plugin renders the template specified in the C<template> |
204 | item in the stash. |
205 | |
206 | sub message : Global { |
94b3529a |
207 | my ( $self, $c ) = @_; |
208 | $c->stash->{template} = 'message.tt2'; |
8cd017a8 |
209 | $c->forward('MyApp::V::TT'); |
210 | } |
722fede4 |
211 | |
94b3529a |
212 | If a class item isn't defined, then it instead uses the |
1bc9fc55 |
213 | current match, as returned by C<< $c->match >>. In the above |
8cd017a8 |
214 | example, this would be C<message>. |
215 | |
216 | The items defined in the stash are passed to the Template Toolkit for |
217 | use as template variables. |
218 | |
8cd017a8 |
219 | sub default : Private { |
94b3529a |
220 | my ( $self, $c ) = @_; |
221 | $c->stash->{template} = 'message.tt2'; |
222 | $c->stash->{message} = 'Hello World!'; |
8cd017a8 |
223 | $c->forward('MyApp::V::TT'); |
224 | } |
7b592fc7 |
225 | |
8cd017a8 |
226 | A number of other template variables are also added: |
8077080c |
227 | |
8cd017a8 |
228 | c A reference to the context object, $c |
229 | base The URL base, from $c->req->base() |
230 | name The application name, from $c->config->{ name } |
231 | |
232 | These can be accessed from the template in the usual way: |
233 | |
234 | <message.tt2>: |
235 | |
236 | The message is: [% message %] |
237 | The base is [% base %] |
238 | The name is [% name %] |
239 | |
8cd017a8 |
240 | |
1bc9fc55 |
241 | The output generated by the template is stored in C<< $c->response->body >>. |
242 | |
243 | =head2 CAPTURING TEMPLATE OUTPUT |
244 | |
245 | If you wish to use the output of a template for some other purpose than |
246 | displaying in the response, e.g. for sending an email, this is possible using |
247 | L<Catalyst::Plugin::Email> and the L<render> method: |
248 | |
249 | sub send_email : Local { |
250 | my ($self, $c) = @_; |
251 | |
252 | $c->email( |
253 | header => [ |
254 | To => 'me@localhost', |
255 | Subject => 'A TT Email', |
256 | ], |
257 | body => $c->view('TT')->render($c, 'email.tt', { |
9d574573 |
258 | additional_template_paths => [ $c->config->{root} . '/email_templates'], |
1bc9fc55 |
259 | email_tmpl_param1 => 'foo' |
260 | } |
261 | ), |
262 | ); |
263 | # Redirect or display a message |
264 | } |
8cd017a8 |
265 | |
266 | =head2 TEMPLATE PROFILING |
267 | |
1bc9fc55 |
268 | See L<C<TIMER>> property of the L<config> method. |
269 | |
caa61517 |
270 | =head2 METHODS |
8077080c |
271 | |
2774dc77 |
272 | =over 4 |
273 | |
274 | =item new |
275 | |
276 | The constructor for the TT view. Sets up the template provider, |
277 | and reads the application config. |
278 | |
8077080c |
279 | =cut |
280 | |
83056300 |
281 | sub _coerce_paths { |
e2bbd784 |
282 | my ( $paths, $dlim ) = shift; |
bec996dc |
283 | return () if ( !$paths ); |
284 | return @{$paths} if ( ref $paths eq 'ARRAY' ); |
9300af5b |
285 | |
bec996dc |
286 | # tweak delim to ignore C:/ |
287 | unless ( defined $dlim ) { |
288 | $dlim = ( $^O eq 'MSWin32' ) ? ':(?!\\/)' : ':'; |
289 | } |
290 | return split( /$dlim/, $paths ); |
291 | } |
9300af5b |
292 | |
e2bbd784 |
293 | sub new { |
294 | my ( $class, $c, $arguments ) = @_; |
8d116b20 |
295 | my $config = { |
296 | EVAL_PERL => 0, |
297 | TEMPLATE_EXTENSION => '', |
298 | %{ $class->config }, |
299 | %{$arguments}, |
300 | }; |
9d574573 |
301 | if ( ! (ref $config->{INCLUDE_PATH} eq 'ARRAY') ) { |
8d116b20 |
302 | my $delim = $config->{DELIMITER}; |
9d574573 |
303 | my @include_path |
304 | = _coerce_paths( $config->{INCLUDE_PATH}, $delim ); |
bec996dc |
305 | if ( !@include_path ) { |
3a4ffa2a |
306 | my $root = $c->config->{root}; |
bec996dc |
307 | my $base = Path::Class::dir( $root, 'base' ); |
3a4ffa2a |
308 | @include_path = ( "$root", "$base" ); |
309 | } |
8d116b20 |
310 | $config->{INCLUDE_PATH} = \@include_path; |
e2bbd784 |
311 | } |
8d116b20 |
312 | |
9d574573 |
313 | |
314 | |
8cd017a8 |
315 | # if we're debugging and/or the TIMER option is set, then we install |
316 | # Template::Timer as a custom CONTEXT object, but only if we haven't |
317 | # already got a custom CONTEXT defined |
318 | |
dccfa0ff |
319 | if ( $config->{TIMER} ) { |
94b3529a |
320 | if ( $config->{CONTEXT} ) { |
8cd017a8 |
321 | $c->log->error( |
1bc9fc55 |
322 | 'Cannot use Template::Timer - a TT CONTEXT is already defined' |
bec996dc |
323 | ); |
8cd017a8 |
324 | } |
325 | else { |
94b3529a |
326 | $config->{CONTEXT} = Template::Timer->new(%$config); |
8cd017a8 |
327 | } |
328 | } |
329 | |
94b3529a |
330 | if ( $c->debug && $config->{DUMP_CONFIG} ) { |
8cd017a8 |
331 | use Data::Dumper; |
94b3529a |
332 | $c->log->debug( "TT Config: ", Dumper($config) ); |
62728755 |
333 | } |
d7e9a1b2 |
334 | if ( $config->{PROVIDERS} ) { |
335 | my @providers = (); |
9d574573 |
336 | if ( ref($config->{PROVIDERS}) eq 'ARRAY') { |
337 | foreach my $p (@{$config->{PROVIDERS}}) { |
d7e9a1b2 |
338 | my $pname = $p->{name}; |
5d8dfd94 |
339 | my $prov = 'Template::Provider'; |
340 | if($pname eq '_file_') |
341 | { |
342 | $p->{args} = { %$config }; |
343 | } |
344 | else |
345 | { |
346 | $prov .="::$pname" if($pname ne '_file_'); |
347 | } |
348 | eval "require $prov"; |
9d574573 |
349 | if(!$@) { |
5d8dfd94 |
350 | push @providers, "$prov"->new($p->{args}); |
351 | } |
352 | else |
353 | { |
354 | $c->log->warn("Can't load $prov, ($@)"); |
d7e9a1b2 |
355 | } |
356 | } |
357 | } |
358 | delete $config->{PROVIDERS}; |
9d574573 |
359 | if(@providers) { |
d7e9a1b2 |
360 | $config->{LOAD_TEMPLATES} = \@providers; |
361 | } |
362 | } |
363 | |
e3a53d5b |
364 | my $self = $class->NEXT::new( |
9d574573 |
365 | $c, { %$config }, |
8cd017a8 |
366 | ); |
9d574573 |
367 | |
368 | # Set base include paths. Local'd in render if needed |
369 | $self->include_path($config->{INCLUDE_PATH}); |
370 | |
e3a53d5b |
371 | $self->config($config); |
372 | |
9d574573 |
373 | # Creation of template outside of call to new so that we can pass [ $self ] |
374 | # as INCLUDE_PATH config item, which then gets ->paths() called to get list |
375 | # of include paths to search for templates. |
d3f19ee8 |
376 | |
377 | # Use a weakend copy of self so we dont have loops preventing GC from working |
378 | my $copy = $self; |
379 | Scalar::Util::weaken($copy); |
380 | $config->{INCLUDE_PATH} = [ sub { $copy->paths } ]; |
9d574573 |
381 | |
9d574573 |
382 | $self->{template} = |
383 | Template->new($config) || do { |
384 | my $error = Template->error(); |
385 | $c->log->error($error); |
386 | $c->error($error); |
387 | return undef; |
388 | }; |
389 | |
390 | |
e3a53d5b |
391 | return $self; |
8077080c |
392 | } |
393 | |
2774dc77 |
394 | =item process |
8077080c |
395 | |
1bc9fc55 |
396 | Renders the template specified in C<< $c->stash->{template} >> or |
397 | C<< $c->action >> (the private name of the matched action. Calls L<render> to |
398 | perform actual rendering. Output is stored in C<< $c->response->body >>. |
8077080c |
399 | |
400 | =cut |
401 | |
402 | sub process { |
403 | my ( $self, $c ) = @_; |
23042c3c |
404 | |
7d8aa5ec |
405 | my $template = $c->stash->{template} |
9d574573 |
406 | || $c->action . $self->config->{TEMPLATE_EXTENSION}; |
23042c3c |
407 | |
408 | unless ($template) { |
8077080c |
409 | $c->log->debug('No template specified for rendering') if $c->debug; |
410 | return 0; |
411 | } |
23042c3c |
412 | |
9d574573 |
413 | my $output = $self->render($c, $template); |
23042c3c |
414 | |
9d574573 |
415 | if (UNIVERSAL::isa($output, 'Template::Exception')) { |
1bc9fc55 |
416 | my $error = qq/Coldn't render template "$output"/; |
8077080c |
417 | $c->log->error($error); |
becb7ac2 |
418 | $c->error($error); |
23042c3c |
419 | return 0; |
420 | } |
bec996dc |
421 | |
23042c3c |
422 | unless ( $c->response->content_type ) { |
423 | $c->response->content_type('text/html; charset=utf-8'); |
8077080c |
424 | } |
23042c3c |
425 | |
426 | $c->response->body($output); |
427 | |
8077080c |
428 | return 1; |
429 | } |
430 | |
1bc9fc55 |
431 | =item render($c, $template, \%args) |
432 | |
433 | Renders the given template and returns output, or a L<Template::Exception> |
434 | object upon error. |
435 | |
436 | The template variables are set to C<%$args> if $args is a hashref, or |
437 | $C<< $c->stash >> otherwise. In either case the variables are augmented with |
438 | C<base> set to C< << $c->req->base >>, C<c> to C<$c> and C<name> to |
439 | C<< $c->config->{name} >>. Alternately, the C<CATALYST_VAR> configuration item |
440 | can be defined to specify the name of a template variable through which the |
441 | context reference (C<$c>) can be accessed. In this case, the C<c>, C<base> and |
442 | C<name> variables are omitted. |
443 | |
444 | C<$template> can be anything that Template::process understands how to |
445 | process, including the name of a template file or a reference to a test string. |
446 | See L<Template::process|Template/process> for a full list of supported formats. |
447 | |
448 | =cut |
449 | |
450 | sub render { |
9d574573 |
451 | my ($self, $c, $template, $args) = @_; |
1bc9fc55 |
452 | |
453 | $c->log->debug(qq/Rendering template "$template"/) if $c->debug; |
454 | |
455 | my $output; |
9d574573 |
456 | my $vars = { |
457 | (ref $args eq 'HASH' ? %$args : %{ $c->stash() }), |
1bc9fc55 |
458 | $self->template_vars($c) |
459 | }; |
460 | |
9d574573 |
461 | local $self->{include_path} = |
462 | [ @{ $vars->{additional_template_paths} }, @{ $self->{include_path} } ] |
463 | if ref $vars->{additional_template_paths}; |
1bc9fc55 |
464 | |
9d574573 |
465 | unless ($self->template->process( $template, $vars, \$output ) ) { |
466 | return $self->template->error; |
467 | } else { |
1bc9fc55 |
468 | return $output; |
469 | } |
470 | } |
471 | |
850ee226 |
472 | =item template_vars |
473 | |
1bc9fc55 |
474 | Returns a list of keys/values to be used as the catalyst variables in the |
850ee226 |
475 | template. |
476 | |
477 | =cut |
478 | |
479 | sub template_vars { |
480 | my ( $self, $c ) = @_; |
481 | |
482 | my $cvar = $self->config->{CATALYST_VAR}; |
483 | |
484 | defined $cvar |
485 | ? ( $cvar => $c ) |
486 | : ( |
487 | c => $c, |
488 | base => $c->req->base, |
489 | name => $c->config->{name} |
9d574573 |
490 | ) |
850ee226 |
491 | } |
1bc9fc55 |
492 | |
2774dc77 |
493 | =item config |
8077080c |
494 | |
8cd017a8 |
495 | This method allows your view subclass to pass additional settings to |
4729c102 |
496 | the TT configuration hash, or to set the options as below: |
497 | |
498 | =over 2 |
499 | |
500 | =item C<CATALYST_VAR> |
501 | |
502 | Allows you to change the name of the Catalyst context object. If set, it will also |
503 | remove the base and name aliases, so you will have access them through <context>. |
504 | |
505 | For example: |
506 | |
507 | MyApp->config({ |
508 | name => 'MyApp', |
7d8aa5ec |
509 | root => MyApp->path_to('root'), |
4729c102 |
510 | 'V::TT' => { |
511 | CATALYST_VAR => 'Catalyst', |
512 | }, |
513 | }); |
514 | |
515 | F<message.tt2>: |
516 | |
517 | The base is [% Catalyst.req.base %] |
518 | The name is [% Catalyst.config.name %] |
519 | |
520 | =item C<TIMER> |
521 | |
522 | If you have configured Catalyst for debug output, and turned on the TIMER setting, |
523 | C<Catalyst::View::TT> will enable profiling of template processing |
524 | (using L<Template::Timer>). This will embed HTML comments in the |
525 | output from your templates, such as: |
526 | |
527 | <!-- TIMER START: process mainmenu/mainmenu.ttml --> |
528 | <!-- TIMER START: include mainmenu/cssindex.tt --> |
529 | <!-- TIMER START: process mainmenu/cssindex.tt --> |
530 | <!-- TIMER END: process mainmenu/cssindex.tt (0.017279 seconds) --> |
531 | <!-- TIMER END: include mainmenu/cssindex.tt (0.017401 seconds) --> |
532 | |
533 | .... |
534 | |
535 | <!-- TIMER END: process mainmenu/footer.tt (0.003016 seconds) --> |
536 | |
537 | |
be7c1afa |
538 | =item C<TEMPLATE_EXTENSION> |
4729c102 |
539 | |
747f2b6d |
540 | a sufix to add when looking for templates bases on the C<match> method in L<Catalyst::Request>. |
4729c102 |
541 | |
542 | For example: |
543 | |
544 | package MyApp::C::Test; |
545 | sub test : Local { .. } |
546 | |
547 | Would by default look for a template in <root>/test/test. If you set TEMPLATE_EXTENSION to '.tt', it will look for |
548 | <root>/test/test.tt. |
549 | |
550 | =back |
8077080c |
551 | |
2774dc77 |
552 | =back |
8077080c |
553 | |
8cd017a8 |
554 | =head2 HELPERS |
555 | |
556 | The L<Catalyst::Helper::View::TT> and |
557 | L<Catalyst::Helper::View::TTSite> helper modules are provided to create |
558 | your view module. There are invoked by the F<myapp_create.pl> script: |
559 | |
560 | $ script/myapp_create.pl view TT TT |
561 | |
562 | $ script/myapp_create.pl view TT TTSite |
563 | |
564 | The L<Catalyst::Helper::View::TT> module creates a basic TT view |
565 | module. The L<Catalyst::Helper::View::TTSite> module goes a little |
566 | further. It also creates a default set of templates to get you |
567 | started. It also configures the view module to locate the templates |
568 | automatically. |
569 | |
8077080c |
570 | =head1 SEE ALSO |
571 | |
8cd017a8 |
572 | L<Catalyst>, L<Catalyst::Helper::View::TT>, |
573 | L<Catalyst::Helper::View::TTSite>, L<Template::Manual> |
8077080c |
574 | |
c0eb0527 |
575 | =head1 AUTHORS |
8077080c |
576 | |
577 | Sebastian Riedel, C<sri@cpan.org> |
c0eb0527 |
578 | |
d938377b |
579 | Marcus Ramberg, C<mramberg@cpan.org> |
c0eb0527 |
580 | |
722fede4 |
581 | Jesse Sheidlower, C<jester@panix.com> |
c0eb0527 |
582 | |
8cd017a8 |
583 | Andy Wardley, C<abw@cpan.org> |
8077080c |
584 | |
585 | =head1 COPYRIGHT |
586 | |
2774dc77 |
587 | This program is free software, you can redistribute it and/or modify it |
588 | under the same terms as Perl itself. |
8077080c |
589 | |
590 | =cut |
591 | |
592 | 1; |