File Coverage

blib/lib/Perl/ToPerl6/TestUtils.pm
Criterion Covered Total %
statement 65 225 28.8
branch 0 94 0.0
condition 0 5 0.0
subroutine 22 38 57.8
pod 13 13 100.0
total 100 375 26.6


line stmt bran cond sub pod time code
1             package Perl::ToPerl6::TestUtils;
2              
3 1     1   154456 use 5.006001;
  1         3  
4 1     1   4 use strict;
  1         2  
  1         19  
5 1     1   4 use warnings;
  1         6  
  1         32  
6              
7 1     1   4 use English qw(-no_match_vars);
  1         2  
  1         7  
8 1     1   1204 use Readonly;
  1         2805  
  1         48  
9              
10 1     1   5 use Exporter 'import';
  1         1  
  1         29  
11              
12 1     1   5 use File::Path ();
  1         1  
  1         14  
13 1     1   4 use File::Spec ();
  1         2  
  1         13  
14 1     1   4 use File::Spec::Unix ();
  1         2  
  1         12  
15 1     1   1041 use File::Temp ();
  1         17060  
  1         27  
16 1     1   5 use File::Find qw( find );
  1         3  
  1         53  
17              
18 1     1   620 use Perl::ToPerl6;
  1         4  
  1         43  
19 1     1   7 use Perl::ToPerl6::Config;
  1         2  
  1         29  
20 1     1   5 use Perl::ToPerl6::Exception::Fatal::Generic qw{ &throw_generic };
  1         3  
  1         92  
21 1     1   6 use Perl::ToPerl6::Exception::Fatal::Internal qw{ &throw_internal };
  1         1  
  1         80  
22 1     1   5 use Perl::ToPerl6::Utils qw{ :severities :data_conversion transformer_long_name };
  1         2  
  1         42  
23 1     1   166 use Perl::ToPerl6::TransformerFactory (-test => 1);
  1         2  
  1         13  
24              
25             Readonly::Array our @EXPORT_OK => qw(
26             ptransform ptransform_with_transformations
27             transform transform_with_transformations
28             ftransform ftransform_with_transformations
29             subtests_in_tree
30             should_skip_author_tests
31             get_author_test_skip_message
32             starting_points_including_examples
33             bundled_transformer_names
34             names_of_transformers_willing_to_work
35             );
36              
37             #-----------------------------------------------------------------------------
38             # If the user already has an existing perlmogrifyrc file, it will get
39             # in the way of these test. This little tweak to ensures that we
40             # don't find the perlmogrifyrc file.
41              
42             sub block_perlmogrifyrc {
43 1     1   5 no warnings 'redefine';
  1         2  
  1         2168  
44 2     2 1 6 *Perl::ToPerl6::UserProfile::_find_profile_path = sub { return };
  1     2   23  
        1      
45 1         3 return 1;
46             }
47              
48             #-----------------------------------------------------------------------------
49             # ToPerl6 a code snippet using only one transformer. Returns the transformations.
50             #
51             # Also uses a secret escape hatch in $c->transform() so we can get at the
52             # raw PPI::Document object without breaking AUTOLOAD.
53              
54             sub ptransform_with_transformations {
55 0     0 1 0 my($transformer, $code_ref, $config_ref) = @_;
56 0         0 my $c = Perl::ToPerl6->new( -profile => 'NONE' );
57 0         0 $c->apply_transform(-transformer => $transformer, -config => $config_ref);
58 0         0 my $doc;
59 0         0 $code_ref = \join("\n",@{$code_ref});
  0         0  
60 0         0 my @rv = $c->transform($code_ref, doc => \$doc);
61 0         0 return ($doc, @rv);
62             }
63              
64             #-----------------------------------------------------------------------------
65             # Mogrifyize a code snippet using only one transformer. Returns the number
66             # of transformations
67              
68             sub ptransform { ##no mogrify(ArgUnpacking)
69 0     0 1 0 return scalar ptransform_with_transformations(@_);
70             }
71              
72             #-----------------------------------------------------------------------------
73             # Mogrifyize a code snippet using a specified config. Returns the transformations.
74              
75             sub transform_with_transformations {
76 0     0 1 0 my ($code_ref, $config_ref) = @_;
77 0         0 my $c = Perl::ToPerl6->new( %{$config_ref} );
  0         0  
78 0         0 return $c->transform($code_ref);
79             }
80              
81             #-----------------------------------------------------------------------------
82             # Mogrifyize a code snippet using a specified config. Returns the
83             # number of transformations
84              
85             sub transform { ##no mogrify(ArgUnpacking)
86 0     0 1 0 return scalar transform_with_transformations(@_);
87             }
88              
89             #-----------------------------------------------------------------------------
90             # Like ptransform_with_transformations, but forces a PPI::Document::File context.
91             # The $filename arg is a Unix-style relative path, like 'Foo/Bar.pm'
92              
93             Readonly::Scalar my $TEMP_FILE_PERMISSIONS => oct 700;
94              
95             sub ftransform_with_transformations {
96 0     0 1 0 my($transformer, $code_ref, $filename, $config_ref) = @_;
97 0         0 my $c = Perl::ToPerl6->new( -profile => 'NONE' );
98 0         0 $c->apply_transform(-transformer => $transformer, -config => $config_ref);
99              
100 0         0 my $dir = File::Temp::tempdir( 'PerlMogrify-tmpXXXXXX', TMPDIR => 1 );
101 0   0     0 $filename ||= 'Temp.pm';
102 0         0 my @fileparts = File::Spec::Unix->splitdir($filename);
103 0 0       0 if (@fileparts > 1) {
104 0         0 my $subdir = File::Spec->catdir($dir, @fileparts[0..$#fileparts-1]);
105 0         0 File::Path::mkpath($subdir, 0, $TEMP_FILE_PERMISSIONS);
106             }
107 0         0 my $file = File::Spec->catfile($dir, @fileparts);
108 0 0       0 if (open my $fh, '>', $file) {
109 0         0 print {$fh} ${$code_ref};
  0         0  
  0         0  
110 0 0       0 close $fh or throw_generic "unable to close $file: $OS_ERROR";
111             }
112              
113             # Use eval so we can clean up before throwing an exception in case of
114             # error.
115 0         0 my @v = eval {$c->transform($file)};
  0         0  
116 0         0 my $err = $EVAL_ERROR;
117 0         0 File::Path::rmtree($dir, 0, 1);
118 0 0       0 if ($err) {
119 0         0 throw_generic $err;
120             }
121 0         0 return @v;
122             }
123              
124             #-----------------------------------------------------------------------------
125             # Like ptransform, but forces a PPI::Document::File context. The
126             # $filename arg is a Unix-style relative path, like 'Foo/Bar.pm'
127              
128             sub ftransform { ##no mogrify(ArgUnpacking)
129 0     0 1 0 return scalar ftransform_with_transformations(@_);
130             }
131              
132             # Note: $include_extras is not documented in the POD because I'm not
133             # committing to the interface yet.
134             sub subtests_in_tree {
135 0     0 1 0 my ($start, $include_extras) = @_;
136              
137 0         0 my %subtests;
138              
139             find(
140             {
141             wanted => sub {
142 0 0   0   0 return if not -f;
143              
144 0         0 my ($fileroot) = m{(.+)[.]run\z}xms;
145              
146 0 0       0 return if not $fileroot;
147              
148 0         0 my @pathparts = File::Spec->splitdir($fileroot);
149 0 0       0 if (@pathparts < 2) {
150 0         0 throw_internal 'confusing transformer test filename ' . $_;
151             }
152              
153             # my $transformer = join q{::}, @pathparts[-2, -1];
154 0         0 my @remaining_path = @pathparts;
155              
156 0 0       0 shift @remaining_path if $remaining_path[0] eq 't';
157 0 0       0 shift @remaining_path if $remaining_path[0] eq 'Perl';
158 0 0       0 shift @remaining_path if $remaining_path[0] eq 'ToPerl6';
159 0         0 my $transformer = join q{::}, @remaining_path;
160              
161 0         0 my $globals = _globals_from_file( $_ );
162 0 0       0 if ( my $prerequisites = $globals->{prerequisites} ) {
163 0         0 foreach my $prerequisite ( keys %{$prerequisites} ) {
  0         0  
164 0 0       0 eval "require $prerequisite; 1" or return;
165             }
166             }
167              
168 0         0 my @subtests = _subtests_from_file( $_ );
169              
170 0 0       0 if ($include_extras) {
171 0         0 $subtests{$transformer} =
172             { subtests => [ @subtests ], globals => $globals };
173             }
174             else {
175 0         0 $subtests{$transformer} = [ @subtests ];
176             }
177              
178 0         0 return;
179             },
180 0         0 no_chdir => 1,
181             },
182             $start
183             );
184              
185 0         0 return \%subtests;
186             }
187              
188             # Answer whether author test should be run.
189             #
190             # Note: this code is duplicated in
191             # t/tlib/Perl/ToPerl6/TestUtilitiesWithMinimalDependencies.pm.
192             # If you change this here, make sure to change it there.
193              
194             sub should_skip_author_tests {
195             return not $ENV{TEST_AUTHOR_PERL_MOGRIFY}
196 0     0 1 0 }
197              
198             sub get_author_test_skip_message {
199 0     0 1 0 return 'Author test. Set $ENV{TEST_AUTHOR_PERL_MOGRIFY} to a true value to run.';
200             }
201              
202              
203             sub starting_points_including_examples {
204 0 0   0 1 0 return (-e 'blib' ? 'blib' : 'lib', 'examples');
205             }
206              
207             sub _globals_from_file {
208 0     0   0 my $test_file = shift;
209              
210 0         0 my %valid_keys = hashify qw< prerequisites >;
211              
212 0 0       0 return if -z $test_file; # Skip if the Transformer has a regular .t file.
213              
214 0         0 my %globals;
215              
216 0 0       0 open my $handle, '<', $test_file
217             or throw_internal "Couldn't open $test_file: $OS_ERROR";
218              
219 0         0 while ( my $line = <$handle> ) {
220 0         0 chomp;
221              
222 0 0       0 if (
223             my ($key,$value) =
224             $line =~ m<\A [#][#] [ ] global [ ] (\S+) (?:\s+(.+))? >xms
225             ) {
226 0 0       0 next if not $key;
227 0 0       0 if ( not $valid_keys{$key} ) {
228 0         0 throw_internal "Unknown global key $key in $test_file";
229             }
230              
231 0 0       0 if ( $key eq 'prerequisites' ) {
232 0         0 $value = { hashify( words_from_string($value) ) };
233             }
234 0         0 $globals{$key} = $value;
235             }
236             }
237 0 0       0 close $handle or throw_generic "unable to close $test_file: $OS_ERROR";
238              
239 0         0 return \%globals;
240             }
241              
242             # The internal representation of a subtest is just a hash with some
243             # named keys. It could be an object with accessors for safety's sake,
244             # but at this point I don't see why.
245             sub _subtests_from_file {
246 0     0   0 my $test_file = shift;
247              
248 0         0 my %valid_keys = hashify qw( name failures parms TODO error filename optional_modules );
249              
250 0 0       0 return if -z $test_file; # Skip if the Transformer has a regular .t file.
251              
252 0 0       0 open my $fh, '<', $test_file
253             or throw_internal "Couldn't open $test_file: $OS_ERROR";
254              
255 0         0 my @subtests;
256              
257 0         0 my $incode = 0;
258 0         0 my $cut_in_code = 0;
259 0         0 my $subtest;
260             my $lineno;
261 0         0 while ( <$fh> ) {
262 0         0 ++$lineno;
263 0         0 chomp;
264 0         0 my $inheader = /^## name/ .. /^## cut/;
265              
266 0         0 my $line = $_;
267              
268 0 0       0 if ( $inheader ) {
    0          
    0          
269 0 0       0 $line =~ m/\A [#]/xms or throw_internal "Code before cut: $test_file";
270 0         0 my ($key,$value) = $line =~ m/\A [#][#] [ ] (\S+) (?:\s+(.+))? /xms;
271 0 0       0 next if !$key;
272 0 0       0 next if $key eq 'cut';
273 0 0       0 if ( not $valid_keys{$key} ) {
274 0         0 throw_internal "Unknown key $key in $test_file";
275             }
276              
277 0 0       0 if ( $key eq 'name' ) {
278 0 0       0 if ( $subtest ) { # Stash any current subtest
279 0         0 push @subtests, _finalize_subtest( $subtest );
280 0         0 undef $subtest;
281             }
282 0         0 $subtest->{lineno} = $lineno;
283 0         0 $incode = 0;
284 0         0 $cut_in_code = 0;
285             }
286 0 0       0 if ($incode) {
287 0         0 throw_internal "Header line found while still in code: $test_file";
288             }
289 0         0 $subtest->{$key} = $value;
290             }
291             elsif ( $subtest ) {
292 0         0 $incode = 1;
293 0   0     0 $cut_in_code ||= $line =~ m/ \A [#][#] [ ] cut \z /smx;
294             # Don't start a subtest if we're not in one.
295             # Don't add to the test if we have seen a '## cut'.
296 0 0       0 $cut_in_code or push @{$subtest->{code}}, $line;
  0         0  
297             }
298             elsif (@subtests) {
299             ## don't complain if we have not yet hit the first test
300 0         0 throw_internal "Got some code but I'm not in a subtest: $test_file";
301             }
302             }
303 0 0       0 close $fh or throw_generic "unable to close $test_file: $OS_ERROR";
304 0 0       0 if ( $subtest ) {
305 0 0       0 if ( $incode ) {
306 0         0 push @subtests, _finalize_subtest( $subtest );
307             }
308             else {
309 0         0 throw_internal "Incomplete subtest in $test_file";
310             }
311             }
312              
313 0         0 return @subtests;
314             }
315              
316             sub _split_subtest {
317 0     0   0 my $code = shift;
318              
319 0         0 my (@original, @sample);
320 0         0 my $original_done;
321 0         0 for ( @{ $code } ) {
  0         0  
322 0 0       0 if ( /^#-->/ ) {
    0          
323 0         0 $original_done = 1;
324 0         0 next;
325             }
326             elsif ( $original_done ) {
327 0         0 push @sample, $_;
328             }
329             else {
330 0         0 push @original, $_;
331             }
332             }
333 0         0 return (\@original, \@sample);
334             }
335              
336             sub _finalize_subtest {
337 0     0   0 my $subtest = shift;
338              
339 0 0       0 if ( $subtest->{code} ) {
340 0         0 my $code = delete $subtest->{code};
341 0         0 @{$subtest}{qw(original sample)} = _split_subtest($code);
  0         0  
342 0         0 delete $subtest->{code};
343             }
344             else {
345 0         0 throw_internal "$subtest->{name} has no code lines";
346             }
347 0 0       0 if ( !defined $subtest->{failures} ) {
348 0         0 throw_internal "$subtest->{name} does not specify failures";
349             }
350 0 0       0 if ($subtest->{parms}) {
351 0         0 $subtest->{parms} = eval $subtest->{parms};
352 0 0       0 if ($EVAL_ERROR) {
353 0         0 throw_internal
354             "$subtest->{name} has an error in the 'parms' property:\n"
355             . $EVAL_ERROR;
356             }
357 0 0       0 if ('HASH' ne ref $subtest->{parms}) {
358 0         0 throw_internal
359             "$subtest->{name} 'parms' did not evaluate to a hashref";
360             }
361             } else {
362 0         0 $subtest->{parms} = {};
363             }
364              
365 0 0       0 if (defined $subtest->{error}) {
366 0 0       0 if ( $subtest->{error} =~ m{ \A / (.*) / \z }xms) {
367 0         0 $subtest->{error} = eval {qr/$1/};
  0         0  
368 0 0       0 if ($EVAL_ERROR) {
369 0         0 throw_internal
370             "$subtest->{name} 'error' has a malformed regular expression";
371             }
372             }
373             }
374              
375 0         0 return $subtest;
376             }
377              
378             sub bundled_transformer_names {
379 1     1 1 1079 require ExtUtils::Manifest;
380 1         8616 my $manifest = ExtUtils::Manifest::maniread();
381 1         3000 my @transformer_paths = map {m{\A lib/(Perl/ToPerl6/Transformer/.*).pm \z}xms} keys %{$manifest};
  187         254  
  1         23  
382 1         10 my @transformers = map { join q{::}, split m{/}xms } @transformer_paths;
  38         137  
383 1         22 my @sorted_transformers = sort @transformers;
384 1         44 return @sorted_transformers;
385             }
386              
387             sub names_of_transformers_willing_to_work {
388 0     0 1   my %configuration = @_;
389              
390 0           my @transformers_willing_to_work =
391             Perl::ToPerl6::Config
392             ->new( %configuration )
393             ->transformers();
394              
395 0           return map { ref } @transformers_willing_to_work;
  0            
396             }
397              
398             1;
399              
400             __END__
401              
402             #-----------------------------------------------------------------------------
403              
404             =pod
405              
406             =for stopwords RCS subtest subtests
407              
408             =head1 NAME
409              
410             Perl::ToPerl6::TestUtils - Utility functions for testing new Transformers.
411              
412              
413             =head1 INTERFACE SUPPORT
414              
415             This is considered to be a public module. Any changes to its
416             interface will go through a deprecation cycle.
417              
418              
419             =head1 SYNOPSIS
420              
421             use Perl::ToPerl6::TestUtils qw(transform ptransform ftransform);
422              
423             my $code = '<<END_CODE';
424             package Foo::Bar;
425             $foo = frobulator();
426             $baz = $foo ** 2;
427             1;
428             END_CODE
429              
430             # Critique code against all loaded transformers...
431             my $perl_mogrify_config = { -necessity => 2 };
432             my $transformation_count = transform( \$code, $perl_mogrify_config);
433              
434             # Critique code against one transformer...
435             my $custom_transformer = 'Miscellanea::ProhibitFrobulation'
436             my $transformation_count = ptransform( $custom_transformer, \$code );
437              
438             # Critique code against one filename-related transformer...
439             my $custom_transformer = 'Modules::RequireFilenameMatchesPackage'
440             my $transformation_count = ftransform( $custom_transformer, \$code, 'Foo/Bar.pm' );
441              
442              
443             =head1 DESCRIPTION
444              
445             This module is used by L<Perl::ToPerl6|Perl::Critic> only for
446             self-testing. It provides a few handy subroutines for testing new
447             Perl::ToPerl6::Transformer modules. Look at the test programs that ship with
448             Perl::ToPerl6 for more examples of how to use these subroutines.
449              
450              
451             =head1 EXPORTS
452              
453             =over
454              
455             =item block_perlmogrifyrc()
456              
457             If a user has a F<~/.perlmogrifyrc> file, this can interfere with
458             testing. This handy method disables the search for that file --
459             simply call it at the top of your F<.t> program. Note that this is
460             not easily reversible, but that should not matter.
461              
462              
463             =item transform_with_transformations( $code_string_ref, $config_ref )
464              
465             Test a block of code against the specified Perl::ToPerl6::Config
466             instance (or C<undef> for the default). Returns the transformations that
467             occurred.
468              
469              
470             =item transform( $code_string_ref, $config_ref )
471              
472             Test a block of code against the specified Perl::ToPerl6::Config
473             instance (or C<undef> for the default). Returns the number of
474             transformations that occurred.
475              
476              
477             =item ptransform_with_transformations( $transformer_name, $code_string_ref, $config_ref )
478              
479             Like C<transform_with_transformations()>, but tests only a single transformer
480             instead of the whole bunch.
481              
482              
483             =item ptransform( $transformer_name, $code_string_ref, $config_ref )
484              
485             Like C<transform()>, but tests only a single transformer instead of the
486             whole bunch.
487              
488              
489             =item ftransform_with_transformations( $transformer_name, $code_string_ref, $filename, $config_ref )
490              
491             Like C<ptransform_with_transformations()>, but pretends that the code was
492             loaded from the specified filename. This is handy for testing
493             transformers like C<Modules::RequireFilenameMatchesPackage> which care
494             about the filename that the source derived from.
495              
496             The C<$filename> parameter must be a relative path, not absolute. The
497             file and all necessary subdirectories will be created via
498             L<File::Temp|File::Temp> and will be automatically deleted.
499              
500              
501             =item ftransform( $transformer_name, $code_string_ref, $filename, $config_ref )
502              
503             Like C<ptransform()>, but pretends that the code was loaded from the
504             specified filename. This is handy for testing transformers like
505             C<Modules::RequireFilenameMatchesPackage> which care about the
506             filename that the source derived from.
507              
508             The C<$filename> parameter must be a relative path, not absolute. The
509             file and all necessary subdirectories will be created via
510             L<File::Temp|File::Temp> and will be automatically deleted.
511              
512              
513             =item subtests_in_tree( $dir )
514              
515             Searches the specified directory recursively for F<.run> files. Each
516             one found is parsed and a hash-of-list-of-hashes is returned. The
517             outer hash is keyed on transformer short name, like
518             C<Modules::RequireEndWithOne>. The inner hash specifies a single test
519             to be handed to C<ptransform()> or C<ftransform()>, including the code
520             string, test name, etc. See below for the syntax of the F<.run>
521             files.
522              
523              
524             =item should_skip_author_tests()
525              
526             Answers whether author tests should run.
527              
528              
529             =item get_author_test_skip_message()
530              
531             Returns a string containing the message that should be emitted when a
532             test is skipped due to it being an author test when author tests are
533             not enabled.
534              
535              
536             =item starting_points_including_examples()
537              
538             Returns a list of the directories contain code that needs to be tested
539             when it is desired that the examples be included.
540              
541              
542             =item bundled_transformer_names()
543              
544             Returns a list of Transformer packages that come bundled with this package.
545             This functions by searching F<MANIFEST> for
546             F<lib/Perl/ToPerl6/Transformer/*.pm> and converts the results to package
547             names.
548              
549              
550             =item names_of_transformers_willing_to_work( %configuration )
551              
552             Returns a list of the packages of transformers that are willing to
553             function on the current system using the specified configuration.
554              
555              
556             =back
557              
558              
559             =head1 F<.run> file information
560              
561             Testing a transformer follows a very simple pattern:
562              
563             * Transformer name
564             * Subtest name
565             * Optional parameters
566             * Number of failures expected
567             * Optional exception expected
568             * Optional filename for code
569              
570             Each of the subtests for a transformer is collected in a single F<.run>
571             file, with test properties as comments in front of each code block
572             that describes how we expect Perl::ToPerl6 to react to the code. For
573             example, say you have a transformer called Variables::ProhibitVowels:
574              
575             (In file t/Variables/ProhibitVowels.run)
576              
577             ## name Basics
578             ## failures 1
579             ## cut
580              
581             my $vrbl_nm = 'foo'; # Good, vowel-free name
582             my $wango = 12; # Bad, pronouncable name
583              
584              
585             ## name Sometimes Y
586             ## failures 1
587             ## cut
588              
589             my $yllw = 0; # "y" not a vowel here
590             my $rhythm = 12; # But here it is
591              
592             These are called "subtests", and two are shown above. The beauty of
593             incorporating multiple subtests in a file is that the F<.run> is
594             itself a (mostly) valid Perl file, and not hidden in a HEREDOC, so
595             your editor's color-coding still works, and it is much easier to work
596             with the code and the POD.
597              
598             If you need to pass any configuration parameters for your subtest, do
599             so like this:
600              
601             ## parms { allow_y => '0' }
602              
603             Note that all the values in this hash must be strings because that's
604             what Perl::ToPerl6 will hand you from a F<.perlmogrifyrc>.
605              
606             If it's a TODO subtest (probably because of some weird corner of PPI
607             that we exercised that Adam is getting around to fixing, right?), then
608             make a C<##TODO> entry.
609              
610             ## TODO Should pass when PPI 1.xxx comes out
611              
612             If the code is expected to trigger an exception in the transformer,
613             indicate that like so:
614              
615             ## error 1
616              
617             If you want to test the error message, mark it with C</.../> to
618             indicate a C<like()> test:
619              
620             ## error /Can't load Foo::Bar/
621              
622             If the transformer you are testing cares about the filename of the code,
623             you can indicate that C<ftransform> should be used like so (see
624             C<ftransform> for more details):
625              
626             ## filename lib/Foo/Bar.pm
627              
628             The value of C<parms> will get C<eval>ed and passed to C<ptransform()>,
629             so be careful.
630              
631             In general, a subtest document runs from the C<## cut> that starts it to
632             either the next C<## name> or the end of the file. In very rare circumstances
633             you may need to end the test document earlier. A second C<## cut> will do
634             this. The only known need for this is in
635             F<t/Miscellanea/RequireRcsKeywords.run>, where it is used to prevent the RCS
636             keywords in the file footer from producing false positives or negatives in the
637             last test.
638              
639             Note that nowhere within the F<.run> file itself do you specify the
640             transformer that you're testing. That's implicit within the filename.
641              
642              
643             =head1 AUTHOR
644              
645             Chris Dolan <cdolan@cpan.org>
646             and the rest of the L<Perl::ToPerl6|Perl::Critic> team.
647              
648              
649             =head1 COPYRIGHT
650              
651             Copyright (c) 2005-2011 Chris Dolan.
652              
653             This program is free software; you can redistribute it and/or modify
654             it under the same terms as Perl itself. The full text of this license
655             can be found in the LICENSE file included with this module.
656              
657             =cut
658              
659             # Local Variables:
660             # mode: cperl
661             # cperl-indent-level: 4
662             # fill-column: 78
663             # indent-tabs-mode: nil
664             # c-indentation-style: bsd
665             # End:
666             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :