clean up term width detection and remove spurious warnings
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Utils.pm
CommitLineData
f05af9ba 1package Catalyst::Utils;
2
3use strict;
37a3ac5c 4use File::Spec;
d837e1a7 5use HTTP::Request;
812a28c9 6use Path::Class;
d837e1a7 7use URI;
5e5bd6df 8use Carp qw/croak/;
a8946dc8 9use Cwd;
e7399d8b 10use Class::Load 'is_class_loaded';
17b3d800 11use String::RewritePrefix;
3086ccde 12use Class::Load ();
0db6e9d5 13use namespace::clean;
ec4d7259 14use Devel::InnerPackage;
cbe627b9 15use Moose::Util;
0db6e9d5 16
f05af9ba 17=head1 NAME
18
19Catalyst::Utils - The Catalyst Utils
20
21=head1 SYNOPSIS
22
23See L<Catalyst>.
24
25=head1 DESCRIPTION
26
b0ad47c1 27Catalyst Utilities.
39fc2ce1 28
f05af9ba 29=head1 METHODS
30
b5ecfcf0 31=head2 appprefix($class)
41ca9ba7 32
85d9fce6 33 MyApp::Foo becomes myapp_foo
41ca9ba7 34
35=cut
36
37sub appprefix {
38 my $class = shift;
0ef447d8 39 $class =~ s/::/_/g;
41ca9ba7 40 $class = lc($class);
41 return $class;
42}
43
b5ecfcf0 44=head2 class2appclass($class);
84cf74e7 45
0ef447d8 46 MyApp::Controller::Foo::Bar becomes MyApp
47 My::App::Controller::Foo::Bar becomes My::App
2d90477f 48
84cf74e7 49=cut
50
51sub class2appclass {
52 my $class = shift || '';
53 my $appname = '';
0ef447d8 54 if ( $class =~ /^(.+?)::([MVC]|Model|View|Controller)::.+$/ ) {
84cf74e7 55 $appname = $1;
56 }
57 return $appname;
58}
59
b5ecfcf0 60=head2 class2classprefix($class);
2930d610 61
0ef447d8 62 MyApp::Controller::Foo::Bar becomes MyApp::Controller
63 My::App::Controller::Foo::Bar becomes My::App::Controller
2d90477f 64
2930d610 65=cut
66
67sub class2classprefix {
68 my $class = shift || '';
69 my $prefix;
0ef447d8 70 if ( $class =~ /^(.+?::([MVC]|Model|View|Controller))::.+$/ ) {
2930d610 71 $prefix = $1;
72 }
73 return $prefix;
74}
75
b5ecfcf0 76=head2 class2classsuffix($class);
84cf74e7 77
0ef447d8 78 MyApp::Controller::Foo::Bar becomes Controller::Foo::Bar
2d90477f 79
84cf74e7 80=cut
81
82sub class2classsuffix {
83 my $class = shift || '';
84 my $prefix = class2appclass($class) || '';
0ef447d8 85 $class =~ s/$prefix\:://;
84cf74e7 86 return $class;
87}
88
b5ecfcf0 89=head2 class2env($class);
3ad654e0 90
26e73131 91Returns the environment name for class.
3ad654e0 92
93 MyApp becomes MYAPP
94 My::App becomes MY_APP
95
96=cut
97
98sub class2env {
99 my $class = shift || '';
0ef447d8 100 $class =~ s/::/_/g;
3ad654e0 101 return uc($class);
102}
103
b5ecfcf0 104=head2 class2prefix( $class, $case );
f05af9ba 105
e2cc89a9 106Returns the uri prefix for a class. If case is false the prefix is converted to lowercase.
f05af9ba 107
0ef447d8 108 My::App::Controller::Foo::Bar becomes foo/bar
2d90477f 109
f05af9ba 110=cut
111
112sub class2prefix {
113 my $class = shift || '';
e494bd6b 114 my $case = shift || 0;
f05af9ba 115 my $prefix;
0ef447d8 116 if ( $class =~ /^.+?::([MVC]|Model|View|Controller)::(.+)$/ ) {
e494bd6b 117 $prefix = $case ? $2 : lc $2;
0ef447d8 118 $prefix =~ s{::}{/}g;
f05af9ba 119 }
120 return $prefix;
121}
122
b5ecfcf0 123=head2 class2tempdir( $class [, $create ] );
37a3ac5c 124
e2cc89a9 125Returns a tempdir for a class. If create is true it will try to create the path.
37a3ac5c 126
127 My::App becomes /tmp/my/app
7d7519a4 128 My::App::Controller::Foo::Bar becomes /tmp/my/app/c/foo/bar
37a3ac5c 129
130=cut
131
132sub class2tempdir {
133 my $class = shift || '';
134 my $create = shift || 0;
4be535b1 135 my @parts = split '::', lc $class;
37a3ac5c 136
137 my $tmpdir = dir( File::Spec->tmpdir, @parts )->cleanup;
138
4be535b1 139 if ( $create && !-e $tmpdir ) {
37a3ac5c 140
ab61f021 141 eval { $tmpdir->mkpath; 1 }
142 or do {
41a8bf1f 143 # don't load Catalyst::Exception as a BEGIN in Utils,
144 # because Utils often gets loaded before MyApp.pm, and if
145 # Catalyst::Exception is loaded before MyApp.pm, it does
146 # not honor setting
147 # $Catalyst::Exception::CATALYST_EXCEPTION_CLASS in
148 # MyApp.pm
149 require Catalyst::Exception;
37a3ac5c 150 Catalyst::Exception->throw(
4be535b1 151 message => qq/Couldn't create tmpdir '$tmpdir', "$@"/ );
37a3ac5c 152 }
153 }
154
155 return $tmpdir->stringify;
156}
157
a8946dc8 158=head2 home($class)
159
160Returns home directory for given class.
161
0f519d62 162=head2 dist_indicator_file_list
163
a8946dc8 164Returns a list of files which can be tested to check if you're inside
e01b6093 165a CPAN distribution which is not yet installed.
166
167These are:
168
169=over
170
171=item Makefile.PL
172
173=item Build.PL
174
175=item dist.ini
176
df221478 177=item L<cpanfile>
178
e01b6093 179=back
0f519d62 180
181=cut
182
183sub dist_indicator_file_list {
df221478 184 qw{Makefile.PL Build.PL dist.ini cpanfile};
0f519d62 185}
186
812a28c9 187sub home {
51f412bd 188 my $class = shift;
189
190 # make an $INC{ $key } style string from the class name
191 (my $file = "$class.pm") =~ s{::}{/}g;
192
193 if ( my $inc_entry = $INC{$file} ) {
51452916 194 {
51f412bd 195 # look for an uninstalled Catalyst app
196
197 # find the @INC entry in which $file was found
198 (my $path = $inc_entry) =~ s/$file$//;
a8946dc8 199 $path ||= cwd() if !defined $path || !length $path;
200 my $home = dir($path)->absolute->cleanup;
201
202 # pop off /lib and /blib if they're there
203 $home = $home->parent while $home =~ /b?lib$/;
204
205 # only return the dir if it has a Makefile.PL or Build.PL or dist.ini
206 if (grep { -f $home->file($_) } dist_indicator_file_list()) {
207 # clean up relative path:
208 # MyApp/script/.. -> MyApp
209
210 my $dir;
211 my @dir_list = $home->dir_list();
212 while (($dir = pop(@dir_list)) && $dir eq '..') {
213 $home = dir($home)->parent->parent;
214 }
215
216 return $home->stringify;
217 }
51452916 218 }
4be535b1 219
51f412bd 220 {
221 # look for an installed Catalyst app
222
223 # trim the .pm off the thing ( Foo/Bar.pm -> Foo/Bar/ )
224 ( my $path = $inc_entry) =~ s/\.pm$//;
225 my $home = dir($path)->absolute->cleanup;
226
bd85860b 227 # return if it's a valid directory
51f412bd 228 return $home->stringify if -d $home;
62459712 229 }
812a28c9 230 }
51f412bd 231
232 # we found nothing
a8946dc8 233 return 0;
03fb1bee 234}
235
b5ecfcf0 236=head2 prefix($class, $name);
812a28c9 237
238Returns a prefixed action.
239
0ef447d8 240 MyApp::Controller::Foo::Bar, yada becomes foo/bar/yada
812a28c9 241
242=cut
243
244sub prefix {
245 my ( $class, $name ) = @_;
246 my $prefix = &class2prefix($class);
247 $name = "$prefix/$name" if $prefix;
248 return $name;
249}
250
b5ecfcf0 251=head2 request($uri)
4d60aa90 252
e2cc89a9 253Returns an L<HTTP::Request> object for a uri.
4d60aa90 254
255=cut
256
257sub request {
258 my $request = shift;
259 unless ( ref $request ) {
a88c7ec8 260 if ( $request =~ m/^http/i ) {
f4c0f6f7 261 $request = URI->new($request);
4d60aa90 262 }
263 else {
f4c0f6f7 264 $request = URI->new( 'http://localhost' . $request );
4d60aa90 265 }
266 }
267 unless ( ref $request eq 'HTTP::Request' ) {
268 $request = HTTP::Request->new( 'GET', $request );
269 }
4d60aa90 270 return $request;
271}
272
dd91afb5 273=head2 ensure_class_loaded($class_name, \%opts)
d9183506 274
275Loads the class unless it already has been loaded.
276
dd91afb5 277If $opts{ignore_loaded} is true always tries the require whether the package
278already exists or not. Only pass this if you're either (a) sure you know the
279file exists on disk or (b) have code to catch the file not found exception
280that will result if it doesn't.
281
d9183506 282=cut
283
284sub ensure_class_loaded {
285 my $class = shift;
d06051f7 286 my $opts = shift;
d9183506 287
5e5bd6df 288 croak "Malformed class Name $class"
289 if $class =~ m/(?:\b\:\b|\:{3,})/;
290
59ede84e 291 croak "Malformed class Name $class"
292 if $class =~ m/[^\w:]/;
293
294 croak "ensure_class_loaded should be given a classname, not a filename ($class)"
295 if $class =~ m/\.pm$/;
296
f55d1491 297 # $opts->{ignore_loaded} can be set to true, and this causes the class to be required, even
298 # if it already has symbol table entries. This is to support things like Schema::Loader, which
299 # part-generate classes in memory, but then also load some of their contents from disk.
d06051f7 300 return if !$opts->{ ignore_loaded }
e7399d8b 301 && is_class_loaded($class); # if a symbol entry exists we don't load again
fbedfd6b 302
d9183506 303 # this hack is so we don't overwrite $@ if the load did not generate an error
304 my $error;
305 {
306 local $@;
7a1958eb 307 my $file = $class . '.pm';
308 $file =~ s{::}{/}g;
309 eval { CORE::require($file) };
d9183506 310 $error = $@;
311 }
6bfff75e 312
d9183506 313 die $error if $error;
fbedfd6b 314
f55d1491 315 warn "require $class was successful but the package is not defined."
e7399d8b 316 unless is_class_loaded($class);
6bfff75e 317
318 return 1;
d9183506 319}
320
358e1592 321=head2 merge_hashes($hashref, $hashref)
322
323Base code to recursively merge two hashes together with right-hand precedence.
324
325=cut
326
327sub merge_hashes {
328 my ( $lefthash, $righthash ) = @_;
329
330 return $lefthash unless defined $righthash;
b0ad47c1 331
358e1592 332 my %merged = %$lefthash;
0ef447d8 333 for my $key ( keys %$righthash ) {
334 my $right_ref = ( ref $righthash->{ $key } || '' ) eq 'HASH';
335 my $left_ref = ( ( exists $lefthash->{ $key } && ref $lefthash->{ $key } ) || '' ) eq 'HASH';
336 if( $right_ref and $left_ref ) {
358e1592 337 $merged{ $key } = merge_hashes(
338 $lefthash->{ $key }, $righthash->{ $key }
0ef447d8 339 );
358e1592 340 }
341 else {
342 $merged{ $key } = $righthash->{ $key };
0ef447d8 343 }
358e1592 344 }
b0ad47c1 345
358e1592 346 return \%merged;
347}
348
cb69249e 349=head2 env_value($class, $key)
350
351Checks for and returns an environment value. For instance, if $key is
352'home', then this method will check for and return the first value it finds,
353looking at $ENV{MYAPP_HOME} and $ENV{CATALYST_HOME}.
354
355=cut
356
357sub env_value {
358 my ( $class, $key ) = @_;
359
360 $key = uc($key);
361 my @prefixes = ( class2env($class), 'CATALYST' );
362
363 for my $prefix (@prefixes) {
364 if ( defined( my $value = $ENV{"${prefix}_${key}"} ) ) {
365 return $value;
366 }
367 }
368
369 return;
370}
d9183506 371
39fc2ce1 372=head2 term_width
373
374Try to guess terminal width to use with formatting of debug output
375
376All you need to get this work, is:
377
3781) Install Term::Size::Any, or
379
b0ad47c1 3802) Export $COLUMNS from your shell.
39fc2ce1 381
382(Warning to bash users: 'echo $COLUMNS' may be showing you the bash
b0ad47c1 383variable, not $ENV{COLUMNS}. 'export COLUMNS=$COLUMNS' and you should see
39fc2ce1 384that 'env' now lists COLUMNS.)
385
386As last resort, default value of 80 chars will be used.
387
ad9e8de9 388Calling C<term_width> with a true value will cause it to be recalculated; you
389can use this to cause it to get recalculated when your terminal is resized like
390this
391
392 $SIG{WINCH} = sub { Catalyst::Utils::term_width(1) };
393
39fc2ce1 394=cut
395
396my $_term_width;
8ace712d 397my $_use_term_size_any;
39fc2ce1 398
399sub term_width {
ad9e8de9 400 my $force_reset = shift;
401
402 undef $_term_width if $force_reset;
403
39fc2ce1 404 return $_term_width if $_term_width;
405
8ace712d 406 if (!defined $_use_term_size_any) {
407 eval {
408 require Term::Size::Any;
409 Term::Size::Any->import();
410 $_use_term_size_any = 1;
411 1;
412 } or do {
413 if ( $@ =~ m[Can't locate Term/Size/Any\.pm] ) {
414 warn "Term::Size::Any is not installed, can't autodetect terminal column width\n";
415 }
416 else {
417 warn "There was an error trying to detect your terminal size: $@\n";
418 }
419 $_use_term_size_any = 0;
420 };
421 }
422
ab61f021 423 my $width;
8ace712d 424
425 if ($_use_term_size_any) {
426 ($width) = Term::Size::Any::chars();
427 }
428
429 if (!$width && $ENV{COLUMNS} && $ENV{COLUMNS} =~ /\A\d+\z/) {
430 $width = $ENV{COLUMNS};
431 }
432 if (!$width || $width < 80) {
433 $width = 80;
b4ed26ff 434 }
39fc2ce1 435
39fc2ce1 436 return $_term_width = $width;
437}
438
17b3d800 439
440=head2 resolve_namespace
441
442Method which adds the namespace for plugins and actions.
443
444 __PACKAGE__->setup(qw(MyPlugin));
196932de 445
17b3d800 446 # will load Catalyst::Plugin::MyPlugin
447
448=cut
449
450
451sub resolve_namespace {
5d8129e9 452 my $appnamespace = shift;
17b3d800 453 my $namespace = shift;
454 my @classes = @_;
196932de 455 return String::RewritePrefix->rewrite({
456 q[] => qq[${namespace}::],
457 q[+] => q[],
458 (defined $appnamespace
459 ? (q[~] => qq[${appnamespace}::])
460 : ()
461 ),
462 }, @classes);
17b3d800 463}
464
3086ccde 465=head2 build_middleware (@args)
466
467Internal application that converts a single middleware definition (see
468L<Catalyst/psgi_middleware>) into an actual instance of middleware.
469
470=cut
471
472sub build_middleware {
473 my ($class, $namespace, @init_args) = @_;
474
475 if(
476 $namespace =~s/^\+// ||
477 $namespace =~/^Plack::Middleware/ ||
478 $namespace =~/^$class/
479 ) { ## the string is a full namespace
480 return Class::Load::try_load_class($namespace) ?
481 $namespace->new(@init_args) :
482 die "Can't load class $namespace";
483 } else { ## the string is a partial namespace
9b5bca00 484 if(Class::Load::try_load_class($class .'::Middleware::'. $namespace)) { ## Load Middleware from Project namespace
485 my $ns = $class .'::Middleware::'. $namespace;
486 return $ns->new(@init_args);
318213cd 487 } elsif(Class::Load::try_load_class("Plack::Middleware::$namespace")) { ## Act like Plack::Builder
488 return "Plack::Middleware::$namespace"->new(@init_args);
d9c6a83f 489 } else {
490 die "Can't load middleware via '$namespace'. It's not ".$class."::Middleware::".$namespace." or Plack::Middleware::$namespace";
3086ccde 491 }
492 }
493
494 return; ## be sure we can count on a proper return when valid
495}
496
497=head2 apply_registered_middleware ($psgi)
498
499Given a $psgi reference, wrap all the L<Catalyst/registered_middlewares>
500around it and return the wrapped version.
501
502This exists to deal with the fact Catalyst registered middleware can be
503either an object with a wrap method or a coderef.
504
505=cut
506
507sub apply_registered_middleware {
508 my ($class, $psgi) = @_;
509 my $new_psgi = $psgi;
510 foreach my $middleware ($class->registered_middlewares) {
511 $new_psgi = Scalar::Util::blessed $middleware ?
512 $middleware->wrap($new_psgi) :
513 $middleware->($new_psgi);
514 }
515 return $new_psgi;
516}
17b3d800 517
ec4d7259 518=head2 inject_component
6adc45cf 519
ec4d7259 520Used to add components at runtime:
521
522 into The Catalyst package to inject into (e.g. My::App)
523 component The component package to inject
38c68f3d 524 traits (Optional) ArrayRef of L<Moose::Role>s that the component should consume.
ec4d7259 525 as An optional moniker to use as the package name for the derived component
526
527For example:
528
529 Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple )
530
531 The above will create 'My::App::Controller::Other::App::Controller::Apple'
532
533 Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple, as => Apple )
534
535 The above will create 'My::App::Controller::Apple'
536
537 Catalyst::Utils::inject_component( into => $myapp, component => 'MyRootV', as => 'Controller::Root' );
538
539Will inject Controller, Model, and View components into your Catalyst application
540at setup (run)time. It does this by creating a new package on-the-fly, having that
541package extend the given component, and then having Catalyst setup the new component
542(via $app->setup_component).
543
544B<NOTE:> This is basically a core version of L<CatalystX::InjectComponent>. If you were using that
545you can now use this safely instead. Going forward changes required to make this work will be
546synchronized with the core method.
547
cbe627b9 548B<NOTE:> The 'traits' option is unique to the L<Catalyst::Utils> version of this feature.
549
550B<NOTE:> These injected components really need to be a L<Catalyst::Component> and a L<Moose>
551based class.
552
ec4d7259 553=cut
554
555sub inject_component {
556 my %given = @_;
557 my ($into, $component, $as) = @given{qw/into component as/};
558
559 croak "No Catalyst (package) given" unless $into;
560 croak "No component (package) given" unless $component;
561
562 Class::Load::load_class($component);
563
564 $as ||= $component;
565 unless ( $as =~ m/^(?:Controller|Model|View)::/ || $given{skip_mvc_renaming} ) {
566 my $category;
567 for (qw/ Controller Model View /) {
568 if ( $component->isa( "Catalyst::$_" ) ) {
569 $category = $_;
570 last;
571 }
572 }
573 croak "Don't know what kind of component \"$component\" is" unless $category;
574 $as = "${category}::$as";
575 }
576 my $component_package = join '::', $into, $as;
577
578 unless ( Class::Load::is_class_loaded $component_package ) {
579 eval "package $component_package; use base qw/$component/; 1;" or
580 croak "Unable to build component package for \"$component_package\": $@";
cbe627b9 581 Moose::Util::apply_all_roles($component_package, @{$given{traits}}) if $given{traits};
ec4d7259 582 (my $file = "$component_package.pm") =~ s{::}{/}g;
88e5a8b0 583 $INC{$file} ||= 1;
ec4d7259 584 }
585
586 my $_setup_component = sub {
587 my $into = shift;
588 my $component_package = shift;
e39312ba 589 $into->components->{$component_package} = $into->delayed_setup_component( $component_package );
ec4d7259 590 };
591
592 $_setup_component->( $into, $component_package );
ec4d7259 593}
6adc45cf 594
9c7b6768 595=head1 PSGI Helpers
596
597Utility functions to make it easier to work with PSGI applications under Catalyst
598
599=head2 env_at_path_prefix
600
601Localize C<$env> under the current controller path prefix:
602
603 package MyApp::Controller::User;
604
605 use Catalyst::Utils;
606
607 use base 'Catalyst::Controller';
608
609 sub name :Local {
610 my ($self, $c) = @_;
611 my $env = $c->Catalyst::Utils::env_at_path_prefix;
612 }
613
efa8265f 614Assuming you have a request like GET /user/name:
9c7b6768 615
616In the example case C<$env> will have PATH_INFO of '/name' instead of
617'/user/name' and SCRIPT_NAME will now be '/user'.
618
619=cut
620
621sub env_at_path_prefix {
622 my $ctx = shift;
623 my $path_prefix = $ctx->controller->path_prefix;
624 my $env = $ctx->request->env;
625 my $path_info = $env->{PATH_INFO};
626 my $script_name = ($env->{SCRIPT_NAME} || '');
627
628 $path_info =~ s/(^\/\Q$path_prefix\E)//;
629 $script_name = "$script_name$1";
630
631 return +{
632 %$env,
633 PATH_INFO => $path_info,
634 SCRIPT_NAME => $script_name };
635}
636
637=head2 env_at_action
638
4477b313 639Localize C<$env> under the current action namespace.
9c7b6768 640
641 package MyApp::Controller::User;
642
643 use Catalyst::Utils;
644
645 use base 'Catalyst::Controller';
646
647 sub name :Local {
648 my ($self, $c) = @_;
649 my $env = $c->Catalyst::Utils::env_at_action;
650 }
651
4477b313 652Assuming you have a request like GET /user/name:
9c7b6768 653
654In the example case C<$env> will have PATH_INFO of '/' instead of
655'/user/name' and SCRIPT_NAME will now be '/user/name'.
656
efa8265f 657Alternatively, assuming you have a request like GET /user/name/foo:
4477b313 658
659In this example case C<$env> will have PATH_INFO of '/foo' instead of
660'/user/name/foo' and SCRIPT_NAME will now be '/user/name'.
661
9c7b6768 662This is probably a common case where you want to mount a PSGI application
663under an action but let the Args fall through to the PSGI app.
664
665=cut
666
667sub env_at_action {
668 my $ctx = shift;
669 my $argpath = join '/', @{$ctx->request->arguments};
670 my $path = '/' . $ctx->request->path;
671
672 $path =~ s/\/?\Q$argpath\E\/?$//;
673
674 my $env = $ctx->request->env;
675 my $path_info = $env->{PATH_INFO};
676 my $script_name = ($env->{SCRIPT_NAME} || '');
677
678 $path_info =~ s/(^\Q$path\E)//;
679 $script_name = "$script_name$1";
680
681 return +{
682 %$env,
683 PATH_INFO => $path_info,
684 SCRIPT_NAME => $script_name };
685}
686
687=head2 env_at_request_uri
688
4477b313 689Localize C<$env> under the current request URI:
9c7b6768 690
691 package MyApp::Controller::User;
692
693 use Catalyst::Utils;
694
695 use base 'Catalyst::Controller';
696
697 sub name :Local Args(1) {
698 my ($self, $c, $id) = @_;
699 my $env = $c->Catalyst::Utils::env_at_request_uri
700 }
701
efa8265f 702Assuming you have a request like GET /user/name/hello:
9c7b6768 703
704In the example case C<$env> will have PATH_INFO of '/' instead of
705'/user/name' and SCRIPT_NAME will now be '/user/name/hello'.
706
707=cut
708
709sub env_at_request_uri {
710 my $ctx = shift;
711 my $path = '/' . $ctx->request->path;
712 my $env = $ctx->request->env;
713 my $path_info = $env->{PATH_INFO};
714 my $script_name = ($env->{SCRIPT_NAME} || '');
715
716 $path_info =~ s/(^\Q$path\E)//;
717 $script_name = "$script_name$1";
718
719 return +{
720 %$env,
721 PATH_INFO => $path_info,
722 SCRIPT_NAME => $script_name };
723}
724
2f381252 725=head1 AUTHORS
f05af9ba 726
2f381252 727Catalyst Contributors, see Catalyst.pm
f05af9ba 728
729=head1 COPYRIGHT
730
536bee89 731This library is free software. You can redistribute it and/or modify it under
f05af9ba 732the same terms as Perl itself.
733
734=cut
735
7361;