File Coverage

blib/lib/App/PodLinkCheck.pm
Criterion Covered Total %
statement 106 295 35.9
branch 24 108 22.2
condition 7 56 12.5
subroutine 17 40 42.5
pod 4 13 30.7
total 158 512 30.8


line stmt bran cond sub pod time code
1             # Copyright 2010, 2011, 2012, 2013 Kevin Ryde
2              
3             # This file is part of PodLinkCheck.
4              
5             # PodLinkCheck is free software; you can redistribute it and/or modify it
6             # under the terms of the GNU General Public License as published by the Free
7             # Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # PodLinkCheck is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with PodLinkCheck. If not, see .
17              
18             package App::PodLinkCheck;
19 1     1   834 use 5.006;
  1         4  
  1         38  
20 1     1   6 use strict;
  1         1  
  1         28  
21 1     1   5 use warnings;
  1         1  
  1         21  
22 1     1   6 use Carp;
  1         1  
  1         94  
23 1     1   1051 use Locale::TextDomain ('App-PodLinkCheck');
  1         45280  
  1         6  
24              
25 1     1   13078 use vars '$VERSION';
  1         3  
  1         593  
26             $VERSION = 12;
27              
28             # uncomment this to run the ### lines
29             # use Smart::Comments;
30              
31             sub command_line {
32 0     0 1 0 my ($self) = @_;
33             ### command_line(): @ARGV
34 0 0       0 ref $self or $self = $self->new;
35              
36 0         0 require Getopt::Long;
37 0         0 Getopt::Long::Configure ('permute', # options with args, callback '<>'
38             'no_ignore_case',
39             'bundling');
40             Getopt::Long::GetOptions
41 0     0   0 ('version' => sub { $self->action_version },
42 0     0   0 'help' => sub { $self->action_help },
43             'verbose:i' => \$self->{'verbose'},
44             'V+' => \$self->{'verbose'},
45             'I=s' => $self->{'extra_INC'},
46              
47             '<>' => sub {
48 0     0   0 my ($value) = @_;
49             # stringize to avoid Getopt::Long object
50 0         0 $self->check_tree ("$value");
51             },
52 0         0 );
53             ### final ARGV: @ARGV
54 0         0 $self->check_tree (@ARGV);
55 0         0 return 0;
56             }
57              
58             sub action_version {
59 0     0 0 0 my ($self) = @_;
60 0         0 print __x("PodLinkCheck version {version}\n", version => $self->VERSION);
61 0 0       0 if ($self->{'verbose'} >= 2) {
62 0         0 require Pod::Simple;
63 0         0 print __x(" Perl version {version}\n", version => $]);
64 0         0 print __x(" Pod::Simple version {version}\n", version => Pod::Simple->VERSION);
65             }
66 0         0 return 0;
67             }
68              
69             sub action_help {
70 0     0 0 0 my ($self) = @_;
71 0         0 require FindBin;
72 1     1   7 no warnings 'once';
  1         2  
  1         579  
73 0         0 my $progname = $FindBin::Script;
74 0         0 print __x("Usage: $progname [--options] file-or-dir...\n");
75 0         0 print __x(" --help print this message\n");
76 0         0 print __x(" --version print version number (and module versions if --verbose=2)\n");
77 0         0 print __x(" --verbose print diagnostic details\n");
78 0         0 print __x(" --verbose=2 print even more diagnostics\n");
79 0         0 return 0;
80             }
81              
82              
83             #------------------------------------------------------------------------------
84              
85             sub new {
86 6     6 1 6108 my ($class, @options) = @_;
87 6         52 return bless { verbose => 0,
88             cpan_methods => ['CPAN_SQLite','CPAN','CPANPLUS'],
89             extra_INC => [],
90             @options }, $class;
91             }
92              
93             sub check_tree {
94 0     0 1 0 my ($self, @files_or_directories) = @_;
95             ### check_tree(): \@files_or_directories
96              
97 0 0       0 my $order = eval { require Sort::Key::Natural }
  0         0  
98             ? \&_find_order_natural : \&_find_order_plain;
99             ### Natural: $@
100              
101 0         0 foreach my $filename (@files_or_directories) {
102 0 0       0 if (-d $filename) {
103 0         0 require File::Find::Iterator;
104 0         0 my $finder = File::Find::Iterator->create (dir => [$filename],
105             order => $order,
106             filter => \&_is_perlfile);
107 0         0 while ($filename = $finder->next) {
108 0         0 print "$filename:\n";
109 0         0 $self->check_file ($filename);
110             }
111             } else {
112 0         0 print "$filename:\n";
113 0         0 $self->check_file ($filename);
114             }
115             }
116              
117             # ### recurse dir: $filename
118             # require File::Find;
119             # File::Find::find ({ wanted => sub {
120             # #### file: $_
121             # if (_is_perlfile()) {
122             # print "$_:\n";
123             # $self->check_file ($_);
124             # }
125             # },
126             # follow_fast => 1,
127             # preprocess => \&_find_sort,
128             # no_chdir => 1,
129             # },
130             # $filename);
131             # } else {
132             # print "$filename:\n";
133             # $self->check_file ($filename);
134             # }
135             # }
136             }
137              
138             sub _is_perlfile {
139             ### _is_perlfile(): $_
140             return (! -d
141 0   0 0   0 && ! m{/\.#} # not emacs lockfile
142             && /\.p([lm]|od)$/);
143             }
144              
145             use constant::defer _HAVE_SORT_KEY_NATURAL => sub {
146 0         0 eval { require Sort::Key::Natural; 1 }
  0         0  
  0         0  
147 1     1   5260 };
  1         907  
  1         11  
148              
149             # sub _find_sort {
150             # # print "_find_sort\n";
151             # return sort _find_order @_;
152             # }
153             sub _find_order_plain {
154 0     0   0 my ($x, $y) = @_;
155             # if $x or $y is a dangling symlink then -d is undef rather than '' false,
156             # hence "||0"
157 0   0     0 return (_cmp_file_before_directory($y,$x)
158             || lc($y) cmp lc($x)
159             || $y cmp $x);
160             }
161             sub _find_order_natural {
162 0     0   0 my ($x, $y) = @_;
163             # if $x or $y is a dangling symlink then -d is undef rather than '' false,
164             # hence "||0"
165             return (_cmp_file_before_directory($y,$x)
166 0   0     0 || do {
167             $x = Sort::Key::Natural::mkkey_natural($x);
168             $y = Sort::Key::Natural::mkkey_natural($y);
169             lc($y) cmp lc($x)
170             || $y cmp $x
171             });
172             }
173             # $x,$y are filenames. Return spaceship style <=> comparision 1,false,-1
174             # which reckons files before directories, ie. file less than directory..
175             sub _cmp_file_before_directory {
176 0     0   0 my ($x, $y) = @_;
177             # If $x or $y is a dangling symlink then -d is undef rather than '' false,
178             # hence "||0" for the compare.
179 0   0     0 return (-d $x || 0) <=> (-d $y || 0);
      0        
180             }
181              
182             sub check_file {
183 0     0 1 0 my ($self, $filename) = @_;
184 0         0 require App::PodLinkCheck::ParseLinks;
185 0         0 my $parser = App::PodLinkCheck::ParseLinks->new ($self);
186 0         0 $parser->parse_from_file ($filename);
187              
188 0         0 my $own_sections = $parser->sections_hashref;
189             ### $own_sections
190              
191 0         0 foreach my $link (@{$parser->links_arrayref}) {
  0         0  
192 0         0 my ($type, $to, $section, $linenum, $column) = @$link;
193              
194 0 0       0 if ($self->{'verbose'}) {
195 0 0       0 print "Link: $type ",(defined $to ? $to : '[undef]'),
    0          
196             (defined $section ? " / $section" : ""), "\n";
197             }
198              
199 0 0       0 if ($type eq 'man') {
200 0 0       0 if (! $self->manpage_is_known($to)) {
201 0         0 $self->report ($filename, $linenum, $column,
202             __x('no man page "{name}"', name => $to));
203             }
204 0         0 next;
205             }
206              
207 0 0       0 if (! defined $to) {
208 0 0 0     0 if (defined $section
209             && ! $own_sections->{$section}) {
210 0 0       0 if (my $approximations
211             = _section_approximations($section,$own_sections)) {
212 0         0 $self->report ($filename, $linenum, $column,
213             __x("no section \"{section}\"\n perhaps it should be {approximations}",
214             section => $section,
215             approximations => $approximations));
216             } else {
217 0         0 $self->report ($filename, $linenum, $column,
218             __x('no section "{section}"',
219             section => $section));
220             }
221 0 0       0 if ($self->{'verbose'} >= 2) {
222 0         0 print __(" available sections:\n");
223 0         0 foreach my $section (keys %$own_sections) {
224 0         0 print " $section\n";
225             }
226             }
227             }
228 0         0 next;
229             }
230              
231 0   0     0 my $podfile = ($self->module_to_podfile($to)
232             || $self->find_script($to));
233             ### $podfile
234 0 0       0 if (! defined $podfile) {
235 0 0       0 if (my $method = $self->_module_known_cpan($to)) {
236 0 0 0     0 if (defined $section && $section ne '') {
237 0         0 $self->report ($filename, $linenum, $column,
238             __x('target "{name}" on cpan ({method}) but no local copy to check section "{section}"',
239             name => $to,
240             method => $method,
241             section => $section));
242             }
243 0         0 next;
244             }
245             }
246              
247 0 0 0     0 if (! defined $podfile
      0        
248             && ! defined $section
249             && $self->manpage_is_known($to)) {
250             # perhaps a script or something we can't find the source but does
251             # have a manpage -- take that as good enough
252 0         0 next;
253             }
254 0 0 0     0 if (! defined $section
      0        
255             && _is_one_word($to)
256             && $own_sections->{$to}) {
257             # one-word internal section
258 0 0       0 if (defined $podfile) {
259             # print "$filename:$linenum:$column: target \"$to\" is both external module/program and internal section\n";
260             } else {
261 0         0 $self->report ($filename, $linenum, $column,
262             __x('internal one-word link recommend guard against ambiguity with {slash} or {quote}',
263             slash => "L",
264             quote => "L<\""._escape_angles($to)."\">"));
265             }
266 0         0 next;
267             }
268 0 0       0 if (! defined $podfile) {
269 0 0       0 if ($own_sections->{$to}) {
270             # multi-word internal section
271 0         0 return;
272             }
273 0         0 $self->report ($filename, $linenum, $column,
274             "no module/program/pod \"$to\"");
275 0         0 next;
276             }
277              
278 0 0 0     0 if (defined $section && $section ne '') {
279 0         0 my $podfile_sections = $self->filename_to_sections ($podfile);
280 0 0       0 if (! $podfile_sections->{$section}) {
281 0 0       0 if (my $approximations
282             = _section_approximations($section,$podfile_sections)) {
283 0         0 $self->report ($filename, $linenum, $column,
284             __x("no section \"{section}\" in \"{name}\" (file {filename})\n perhaps it should be {approximations}",
285             name => $to,
286             section => $section,
287             filename => $podfile,
288             approximations => $approximations));
289             } else {
290 0         0 $self->report ($filename, $linenum, $column,
291             __x('no section "{section}" in "{name}" (file {filename})',
292             name => $to,
293             section => $section,
294             filename => $podfile));
295             }
296 0 0       0 if ($self->{'verbose'} >= 2) {
297 0         0 print __(" available sections:\n");
298 0         0 foreach my $section (keys %$podfile_sections) {
299 0         0 print " $section\n";
300             }
301             }
302             }
303             }
304             }
305             }
306              
307             sub report {
308 0     0 0 0 my ($self, $filename, $linenum, $column, $message) = @_;
309 0         0 print "$filename:$linenum:$column: $message\n";
310             }
311              
312             # return a string of close matches of $section in the keys of %$hashref
313             sub _section_approximations {
314 0     0   0 my ($section, $hashref) = @_;
315 0         0 $section = _section_approximation_crunch($section);
316 0         0 return join(' or ',
317 0         0 map {"\"$_\""}
318 0         0 grep {_section_approximation_crunch($_) eq $section}
319             keys %$hashref);
320             }
321             sub _section_approximation_crunch {
322 0     0   0 my ($section) = @_;
323 0         0 $section =~ s/(\W|_)+//g;
324 0         0 return lc($section);
325             }
326              
327             sub _is_one_word {
328 0     0   0 my ($link) = @_;
329 0         0 return ($link !~ /\W/);
330             }
331             sub _escape_angles {
332 3     3   1717 my ($str) = @_;
333 3         15 $str =~ s{([<>])}
334 4 100       20 { 'E<'.($1 eq '<' ? 'lt' : 'gt').'>' }ge;
335 3         19 return $str;
336             }
337              
338             sub module_to_podfile {
339 0     0 0 0 my ($self, $module) = @_;
340 0         0 require Pod::Find;
341 0         0 return Pod::Find::pod_where ({ '-dirs' => $self->{'extra_INC'},
342             '-inc' => 1,
343             },
344             $module);
345             }
346              
347             # return hashref
348             sub filename_to_sections {
349 0     0 0 0 my ($self, $filename) = @_;
350 0   0     0 return ($self->{'sections_cache'}->{$filename} ||= do {
351             ### parse file for sections: $filename
352 0         0 my $parser = App::PodLinkCheck::ParseSections->new;
353 0         0 $parser->parse_file ($filename);
354             ### file sections: $parser->sections_hashref
355 0         0 $parser->sections_hashref;
356             });
357             }
358              
359             #------------------------------------------------------------------------------
360             # CPAN
361             #
362             # cf CPAN::API::HOWTO
363              
364             sub _module_known_cpan {
365 0     0   0 my ($self, $module) = @_;
366 0         0 foreach my $method (@{$self->{'cpan_methods'}}) {
  0         0  
367 0         0 my $fullmethod = "_module_known_$method";
368 0 0       0 if ($self->$fullmethod ($module)) {
369 0         0 return $method;
370             }
371             }
372 0         0 return 0;
373             }
374              
375             use constant::defer _CPAN_config => sub {
376 1         19 my $result = 0;
377 1 50       3 eval {
378 1         5853 require CPAN;
379             # not sure how far back this will work, maybe only 5.8.0 up
380 1 50 33     362731 if (! $CPAN::Config_loaded
381             && CPAN::HandleConfig->can('load')) {
382             # fake $loading to avoid running the CPAN::FirstTime dialog -- is
383             # this the right way to do that?
384 1         11 local $CPAN::HandleConfig::loading = 1;
385 1         22 print __x("PodLinkCheck: {module} configs\n",
386             module => 'CPAN');
387 1         1055 CPAN::HandleConfig->load;
388             }
389 1         469 $result = 1;
390             }
391             or print "CPAN.pm config error: $@\n";
392 1         18 return $result;
393 1     1   2778 };
  1         5  
  1         10  
394              
395             sub _module_known_CPAN_SQLite {
396 3     3   869 my ($self, $module) = @_;
397              
398 3 100       26 if (! defined $self->{'cpan_sqlite'}) {
399 1         4 $self->{'cpan_sqlite'} = 0;
400              
401 1 50       9 if ($self->_CPAN_config) {
402 1         94 print __x("PodLinkCheck: loading {module} for module existence checking\n",
403             module => 'CPAN::SQLite');
404 1 50       410 if (! eval { require CPAN::SQLite }) {
  1         394  
405 1         17 print __x("Cannot load {module}, skipping -- {error}\n",
406             module => 'CPAN::SQLite',
407             error => $@);
408 1         243 return 0;
409             }
410 0 0       0 if (! eval {
411             # fake $loading to avoid running the CPAN::FirstTime dialog -- is
412             # this the right way to do that?
413 0         0 local $CPAN::HandleConfig::loading = 1;
414 0         0 $self->{'cpan_sqlite'} = CPAN::SQLite->new (update_indices => 0);
415             }) {
416 0         0 print __x("{module} error: {error}\n",
417             module => 'CPAN::SQLite',
418             error => $@);
419             }
420             }
421             }
422              
423 2   50     26 my $cpan_sqlite = $self->{'cpan_sqlite'} || return 0;
424              
425             # Have struck errors from cpantesters creating db tables. Not sure if it
426             # might happen in a real run. Guard with an eval.
427             #
428 0         0 my $result;
429 0 0       0 if (! eval { $result = $cpan_sqlite->query (mode => 'module',
  0         0  
430             name => $module);
431 0         0 1 }) {
432 0         0 print __x("{module} error, disabling -- {error}\n",
433             module => 'CPAN::SQLite',
434             error => $@);
435 0         0 $self->{'cpan_sqlite'} = 0;
436 0         0 return 0;
437             }
438 0         0 return $result;
439             }
440              
441             my $use_CPAN;
442             sub _module_known_CPAN {
443 3     3   1099 my ($self, $module) = @_;
444             ### _module_known_CPAN(): $module
445              
446 3 100       11 if (! defined $use_CPAN) {
447 1         2 $use_CPAN = 0;
448              
449 1 50       4 if ($self->_CPAN_config) {
450 1 50       2 eval {
451 1         4 print __x("PodLinkCheck: load {module} for module existence checking\n",
452             module => 'CPAN');
453              
454 1 50 33     1731 if (defined $CPAN::META && %$CPAN::META) {
    50          
455 0         0 $use_CPAN = 1;
456             } elsif (! CPAN::Index->can('read_metadata_cache')) {
457 0         0 print __("PodLinkCheck: no Metadata cache in this CPAN.pm\n");
458             } else {
459             # try the .cpan/Metadata even if CPAN::SQLite is installed, just in
460             # case the SQLite is not up-to-date or has not been used yet
461 1         4 local $CPAN::Config->{use_sqlite} = 0;
462 1         11 CPAN::Index->read_metadata_cache;
463 1 50 33     2787071 if (defined $CPAN::META && %$CPAN::META) {
464 1         5 $use_CPAN = 1;
465             } else {
466 0         0 print __("PodLinkCheck: empty Metadata cache\n");
467             }
468             }
469 1         7 1;
470             }
471             or print "CPAN.pm error: $@\n";
472             }
473             }
474              
475 3   66     40 return ($use_CPAN
476             && exists($CPAN::META->{'readwrite'}->{'CPAN::Module'}->{$module}));
477             }
478              
479             sub _module_known_CPANPLUS {
480 3     3   914 my ($self, $module) = @_;
481             ### _module_known_CPANPLUS(): $module
482              
483 3 100       11 if (! defined $self->{'cpanplus'}) {
484 1         7 print __x("PodLinkCheck: load {module} for module existence checking\n",
485             module => 'CPANPLUS');
486 1 50       225 if (! eval { require CPANPLUS::Backend;
  1         412  
487 0         0 require CPANPLUS::Configure;
488             }) {
489 1         9 $self->{'cpanplus'} = 0;
490 1         6 print __x("Cannot load {module}, skipping -- {error}\n",
491             module => 'CPANPLUS',
492             error => $@);
493 1         292 return 0;
494             }
495 0         0 my $conf = CPANPLUS::Configure->new;
496 0         0 $conf->set_conf (verbose => 1);
497 0         0 $conf->set_conf (no_update => 1);
498 0         0 $self->{'cpanplus'} = CPANPLUS::Backend->new ($conf);
499             }
500              
501 2   50     12 my $cpanplus = $self->{'cpanplus'} || return 0;
502              
503             # module_tree() returns false '' for not found.
504             #
505             # Struck an error from module_tree() somehow relating to
506             # CPANPLUS::Internals::Source::SQLite on cpantesters at one time, so guard
507             # with an eval.
508             #
509 0         0 my $result;
510 0 0       0 if (! eval { $result = $cpanplus->module_tree($module); 1 }) {
  0         0  
  0         0  
511 0         0 print __x("{module} error, disabling -- {error}\n",
512             module => 'CPANPLUS',
513             error => $@);
514 0         0 $self->{'cpanplus'} = 0;
515 0         0 return 0;
516             }
517 0         0 return $result;
518             }
519              
520             #------------------------------------------------------------------------------
521             # PATH
522              
523             sub find_script {
524 0     0 0 0 my ($self, $name) = @_;
525 0         0 foreach my $dir ($self->PATH_list) {
526 0         0 my $filename = File::Spec->catfile($dir,$name);
527             #### $filename
528 0 0       0 if (-e $filename) {
529 0         0 return $filename;
530             }
531             }
532 0         0 return undef;
533             }
534              
535             # return list of directories
536             sub PATH_list {
537 0     0 0 0 my ($self) = @_;
538 0         0 require Config;
539 0         0 return split /\Q$Config::Config{'path_sep'}/o, $self->PATH;
540             }
541              
542             # return string
543             sub PATH {
544 0     0 0 0 my ($self) = @_;
545 0 0       0 if (defined $self->{'PATH'}) {
546 0         0 return $self->{'PATH'};
547             } else {
548 0         0 return $ENV{'PATH'};
549             }
550             }
551              
552             #------------------------------------------------------------------------------
553             # man
554              
555             # return bool
556             sub manpage_is_known {
557 6     6 0 2954 my ($self, $name) = @_;
558 6         11 my @manargs;
559 6         10 my $section = '';
560 6 100       32 if ($name =~ s/\s*\((.+)\)$//) {
561 2         6 $section = $1;
562 2         6 @manargs = ($section);
563             }
564              
565 6         33 my $r = \$self->{'manpage_is_known'}->{$section}->{$name};
566 6 50       22 if (defined $$r) {
567 0         0 return $$r;
568             }
569 6         13 push @manargs, $name;
570             ### man: \@manargs
571              
572 6 50       29 return ($$r = ($self->_man_has_location_option()
573             ? $self->_manpage_is_known_by_location(@manargs)
574             : $self->_manpage_is_known_by_output(@manargs)));
575             }
576              
577             # --location is not in posix,
578             # http://www.opengroup.org/onlinepubs/009695399/utilities/man.html
579             # Is it man-db specific, or does it have a chance of working elsewhere?
580             #
581             use constant::defer _man_has_location_option => sub {
582 1         19 my ($self) = @_;
583             ### _man_has_location_option() ...
584 1         1631 require IPC::Run;
585 1         54803 require File::Spec;
586 1         4 my $str = '';
587 1         2 eval {
588 1         23 IPC::Run::run (['man','--help'],
589             '<', \undef,
590             '>', \$str,
591             '2>', File::Spec->devnull);
592             };
593 1         1437 my $ret = ($str =~ /--location\b/);
594 1 50       10 if ($self->{'verbose'} >= 2) {
595 0 0       0 if ($ret) {
596 0         0 print __("man \"--location\" option is available\n");
597             } else {
598 0         0 print __("man \"--location\" option not available (not in its \"--help\")\n");
599             }
600             }
601             ### $ret
602 1         6 return $ret;
603 1     1   1549 };
  1         2  
  1         7  
604              
605             sub _manpage_is_known_by_location {
606 0     0   0 my ($self, @manargs) = @_;
607             ### _manpage_is_known_by_location() run: \@manargs
608 0         0 require IPC::Run;
609 0         0 my $str;
610 0 0       0 if (! eval {
611 0         0 IPC::Run::run (['man', '--location', @manargs],
612             '<', \undef, # stdin
613             '>', \$str, # stdout
614             '2>', File::Spec->devnull);
615 0         0 1;
616             }) {
617 0         0 my $err = $@;
618 0         0 $err =~ s/\s+$//;
619 0         0 print __x("PodLinkCheck: error running 'man': {error}\n", error => $err);
620 0         0 return 0;
621             }
622             ### _manpage_is_known_by_location() output: $str
623 0 0       0 return ($str =~ /\n/ ? 1 : 0);
624             }
625              
626             sub _manpage_is_known_by_output {
627 6     6   67 my ($self, @manargs) = @_;
628             ### _manpage_is_known_by_output() run: \@manargs
629 6         37 require IPC::Run;
630 6         3733 require File::Temp;
631 6         13984 my $fh = File::Temp->new (TEMPLATE => 'PodLinkCheck-man-XXXXXX',
632             TMPDIR => 1);
633 6 50       2820 if (! eval {
634 6         61 IPC::Run::run (['man', @manargs],
635             '<', \undef, # stdin
636             '>', $fh, # stdout
637             '2>', File::Spec->devnull);
638 0         0 1;
639             }) {
640 6         9113 my $err = $@;
641 6         42 $err =~ s/\s+$//;
642 6         29 print __x("PodLinkCheck: error running 'man': {error}\n", error => $err);
643 6         1863 return 0;
644             }
645              
646 0           seek $fh, 0, 0;
647 0           foreach (1 .. 5) {
648 0 0         if (! defined (readline $fh)) {
649 0           return 0;
650             }
651             }
652 0           return 1;
653             }
654              
655             1;
656             __END__