File Coverage

blib/lib/Perl/Critic/Policy/Documentation/PodSpelling.pm
Criterion Covered Total %
statement 60 134 44.7
branch 5 42 11.9
condition 1 6 16.6
subroutine 21 32 65.6
pod 5 7 71.4
total 92 221 41.6


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Documentation::PodSpelling;
2              
3 40     40   28108 use 5.010001;
  40         162  
4 40     40   222 use strict;
  40         106  
  40         817  
5 40     40   194 use warnings;
  40         91  
  40         1188  
6              
7 40     40   232 use English qw(-no_match_vars);
  40         108  
  40         259  
8 40     40   14470 use Readonly;
  40         120  
  40         1745  
9              
10 40     40   284 use File::Spec;
  40         91  
  40         1094  
11 40     40   11161 use File::Temp;
  40         119256  
  40         3626  
12 40     40   314 use List::SomeUtils qw(uniq);
  40         93  
  40         2014  
13 40     40   20635 use Pod::Spell qw< >;
  40         1331568  
  40         1093  
14 40     40   18386 use Pod::Wordlist;
  40         15194377  
  40         2500  
15 40     40   27850 use Text::ParseWords qw< >;
  40         59849  
  40         1378  
16              
17 40         4024 use Perl::Critic::Utils qw(
18             :characters
19             :booleans
20             :severities
21 40     40   364 );
  40         104  
22 40     40   16562 use Perl::Critic::Exception::Fatal::Generic qw{ throw_generic };
  40         129  
  40         2746  
23              
24 40     40   321 use parent 'Perl::Critic::Policy';
  40         119  
  40         451  
25              
26             our $VERSION = '1.150';
27              
28             #-----------------------------------------------------------------------------
29              
30             Readonly::Scalar my $DESC => q{Check the spelling in your POD};
31             Readonly::Scalar my $EXPL => [148];
32              
33             #-----------------------------------------------------------------------------
34              
35             sub supported_parameters {
36             return (
37             {
38 94     94 0 2715 name => 'spell_command',
39             description => 'The command to invoke to check spelling.',
40             default_string => 'aspell list',
41             behavior => 'string',
42             },
43             {
44             name => 'stop_words',
45             description => 'The words to not consider as misspelled.',
46             default_string => $EMPTY,
47             behavior => 'string list',
48             },
49             {
50             name => 'stop_words_file',
51             description => 'A file containing words to not consider as misspelled.',
52             default_string => $EMPTY,
53             behavior => 'string',
54             },
55             );
56             }
57              
58 84     84 1 362 sub default_severity { return $SEVERITY_LOWEST }
59 89     89 1 405 sub default_themes { return qw( core cosmetic pbp ) }
60 0     0 1 0 sub applies_to { return 'PPI::Document' }
61              
62             #-----------------------------------------------------------------------------
63              
64             my $got_sigpipe = 0;
65             sub got_sigpipe {
66 0     0 0 0 return $got_sigpipe;
67             }
68              
69             #-----------------------------------------------------------------------------
70              
71             sub initialize_if_enabled {
72 22     22 1 112 my ( $self, $config ) = @_;
73              
74 22 50       67 eval { require File::Which; 1 } or return $FALSE;
  22         3707  
  22         6124  
75              
76 22 50       130 return $FALSE if not $self->_derive_spell_command_line();
77              
78 0         0 my $test_code = <<'END_TEST_CODE';
79             ;pod
80              
81             ;head1 Test The Spell Command
82              
83             ;cut
84             END_TEST_CODE
85 0         0 $test_code =~ s/^;/=/msx;
86 0 0       0 return $FALSE if not $self->_run_spell_command($test_code);
87              
88 0         0 $self->_load_stop_words_file();
89              
90 0         0 return $TRUE;
91             }
92              
93             #-----------------------------------------------------------------------------
94              
95             sub violates {
96 0     0 1 0 my ( $self, $elem, $doc ) = @_;
97              
98 0         0 my $code = $doc->serialize();
99              
100 0         0 my $words = $self->_run_spell_command($code);
101              
102 0 0       0 return if not $words; # error running spell command
103              
104 0 0       0 return if not @{$words}; # no problems found
  0         0  
105              
106 0         0 return $self->violation( "$DESC: @{$words}", $EXPL, $doc );
  0         0  
107             }
108              
109             #-----------------------------------------------------------------------------
110              
111             sub _derive_spell_command_line {
112 22     22   63 my ($self) = @_;
113              
114 22         110 my @words = Text::ParseWords::shellwords($self->_get_spell_command());
115 22 50       3007 if (!@words) {
116 0         0 return;
117             }
118 22 50       326 if (! File::Spec->file_name_is_absolute($words[0])) {
119 22         130 $words[0] = File::Which::which($words[0]);
120             }
121 22 50 33     6064 if (! $words[0] || ! -x $words[0]) {
122 22         171 return;
123             }
124 0         0 $self->_set_spell_command_line(\@words);
125              
126 0         0 return $self->_get_spell_command_line();
127             }
128              
129             #-----------------------------------------------------------------------------
130              
131             sub _get_spell_command {
132 22     22   73 my ( $self ) = @_;
133              
134 22         213 return $self->{_spell_command};
135             }
136              
137             #-----------------------------------------------------------------------------
138              
139             sub _get_spell_command_line {
140 1     1   8 my ( $self ) = @_;
141              
142 1         28 return $self->{_spell_command_line};
143             }
144              
145             sub _set_spell_command_line {
146 0     0     my ( $self, $spell_command_line ) = @_;
147              
148 0           $self->{_spell_command_line} = $spell_command_line;
149              
150 0           return;
151             }
152              
153             #-----------------------------------------------------------------------------
154              
155             sub _get_stop_words {
156 0     0     my ( $self ) = @_;
157              
158 0           return $self->{_stop_words};
159             }
160              
161             sub _set_stop_words {
162 0     0     my ( $self, $stop_words ) = @_;
163              
164 0           $self->{_stop_words} = $stop_words;
165              
166 0           return;
167             }
168              
169             #-----------------------------------------------------------------------------
170              
171             sub _get_stop_words_file {
172 0     0     my ( $self ) = @_;
173              
174 0           return $self->{_stop_words_file};
175             }
176              
177             #-----------------------------------------------------------------------------
178              
179             sub _run_spell_command {
180 0     0     my ($self, $code) = @_;
181              
182 0           my $outfh = File::Temp->new();
183              
184 0           my $outfile = $outfh->filename();
185 0           my @words;
186              
187 0           local $EVAL_ERROR = undef;
188              
189             eval {
190             # temporarily add our special wordlist to this annoying global
191             local %Pod::Wordlist::Wordlist = ## no critic (ProhibitPackageVars)
192 0           %{ $self->_get_stop_words() };
  0            
193              
194 0 0         open my $infh, '<', \$code
195             or throw_generic "error opening scalar: $OS_ERROR";
196              
197 0           Pod::Spell->new()->parse_from_filehandle($infh, $outfh);
198 0 0         close $infh or throw_generic "Failed to close in memory file: $OS_ERROR";
199 0 0         close $outfh or throw_generic "Failed to close pod temp file: $OS_ERROR";
200 0 0         return if not -s $outfile; # Bail out if no words to spellcheck
201              
202             # run spell command and fetch output
203 0     0     local $SIG{PIPE} = sub { $got_sigpipe = 1; };
  0            
204 0           my $command_line = join $SPACE, @{$self->_get_spell_command_line()};
  0            
205 0 0         open my $aspell_out_fh, q{-|}, "$command_line < $outfile" ## Is this portable??
206             or throw_generic "Failed to open handle to spelling program: $OS_ERROR";
207              
208 0           @words = uniq( <$aspell_out_fh> );
209 0 0         close $aspell_out_fh
210             or throw_generic "Failed to close handle to spelling program: $OS_ERROR";
211              
212 0           chomp @words;
213              
214             # Why is this extra step needed???
215 0           @words = grep { not exists $Pod::Wordlist::Wordlist{$_} } @words; ## no critic (ProhibitPackageVars)
  0            
216 0           1;
217             }
218 0 0         or do {
219             # Eat anything we did ourselves above, propagate anything else.
220 0 0 0       if (
221             $EVAL_ERROR
222             and not ref Perl::Critic::Exception::Fatal::Generic->caught()
223             ) {
224 0 0         ref $EVAL_ERROR ? $EVAL_ERROR->rethrow() : die $EVAL_ERROR; ## no critic (ErrorHandling::RequireCarping)
225             }
226              
227 0           return;
228             };
229              
230 0           return [ @words ];
231             }
232              
233             #-----------------------------------------------------------------------------
234              
235             sub _load_stop_words_file {
236 0     0     my ($self) = @_;
237              
238 0           my %stop_words = %{ $self->_get_stop_words() };
  0            
239              
240 0 0         my $file_name = $self->_get_stop_words_file() or return;
241              
242             open my $handle, '<', $file_name
243 0 0         or do { warn qq<Could not open "$file_name": $OS_ERROR\n>; return; };
  0            
  0            
244              
245 0           while ( my $line = <$handle> ) {
246 0 0         if ( my $word = _word_from_line($line) ) {
247 0           $stop_words{$word} = 1;
248             }
249             }
250              
251 0 0         close $handle or warn qq<Could not close "$file_name": $OS_ERROR\n>;
252              
253 0           $self->_set_stop_words(\%stop_words);
254              
255 0           return;
256             }
257              
258             sub _word_from_line {
259 0     0     my ($line) = @_;
260              
261 0           $line =~ s< [#] .* \z ><>xms;
262 0           $line =~ s< \s+ \z ><>xms;
263 0           $line =~ s< \A \s+ ><>xms;
264              
265 0           return $line;
266             }
267              
268             #-----------------------------------------------------------------------------
269              
270             1;
271              
272             __END__
273              
274             #-----------------------------------------------------------------------------
275              
276             =pod
277              
278             =for stopwords foobie foobie-bletch Hmm stopwords
279              
280             =head1 NAME
281              
282             Perl::Critic::Policy::Documentation::PodSpelling - Check your spelling.
283              
284              
285             =head1 AFFILIATION
286              
287             This Policy is part of the core L<Perl::Critic|Perl::Critic>
288             distribution.
289              
290              
291             =head1 DESCRIPTION
292              
293             Did you write the documentation? Check.
294              
295             Did you document all of the public methods? Check.
296              
297             Is your documentation readable? Hmm...
298              
299             Ideally, we'd like Perl::Critic to tell you when your documentation is
300             inadequate. That's hard to code, though. So, inspired by
301             L<Test::Spelling|Test::Spelling>, this module checks the spelling of
302             your POD. It does this by pulling the prose out of the code and
303             passing it to an external spell checker. It skips over words you
304             flagged to ignore. If the spell checker returns any misspelled words,
305             this policy emits a violation.
306              
307             If anything else goes wrong -- we can't locate the spell checking program or
308             (gasp!) your module has no POD -- then this policy passes.
309              
310             To add exceptions on a module-by-module basis, add "stopwords" as
311             described in L<Pod::Spell|Pod::Spell>. For example:
312              
313             =for stopwords gibbles
314              
315             =head1 Gibble::Manip -- manipulate your gibbles
316              
317             =cut
318              
319              
320             =head1 CONFIGURATION
321              
322             This policy can be configured to tell which spell checker to use or to
323             set a global list of spelling exceptions. To do this, put entries in
324             a F<.perlcriticrc> file like this:
325              
326             [Documentation::PodSpelling]
327             spell_command = aspell list
328             stop_words = gibbles foobar
329             stop_words_file = some/path/with/stop/words.txt
330              
331             The default spell command is C<aspell list> and it is interpreted as a
332             shell command. We parse the individual arguments via
333             L<Text::ParseWords|Text::ParseWords> so feel free to use quotes around
334             your arguments. If the executable path is an absolute file name, it
335             is used as-is. If it is a relative file name, we employ
336             L<File::Which|File::Which> to convert it to an absolute path via the
337             C<PATH> environment variable. As described in Pod::Spell and
338             Test::Spelling, the spell checker must accept text on STDIN and print
339             misspelled words one per line on STDOUT.
340              
341             You can specify global stop words via the C<stop_words> and
342             C<stop_words_file> options. The former is simply split up on
343             whitespace. The latter is looked at line by line, with anything after
344             an octothorp ("#") removed and then leading and trailing whitespace
345             removed. Silly example valid file contents:
346              
347             # It's a comment!
348              
349             foo
350             arglbargl # Some other comment.
351             bar
352              
353             The values from C<stop_words> and C<stop_words_file> are merged
354             together into a single list of exemptions.
355              
356              
357             =head1 NOTES
358              
359             A spell checking program is not included with Perl::Critic.
360              
361             The results of failures for this policy can be confusing when F<aspell>
362             complains about words containing punctuation such as hyphens and apostrophes.
363             In this situation F<aspell> will often only emit part of the word that it
364             thinks is misspelled. For example, if you ask F<aspell> to check
365             "foobie-bletch", the output only complains about "foobie". Unfortunately,
366             you'll have to look through your POD to figure out what the real word that
367             F<aspell> is complaining about is. One thing to try is looking at the output
368             of C<< perl -MPod::Spell -e 'print
369             Pod::Spell->new()->parse_from_file("lib/Your/Module.pm")' >> to see what is
370             actually being checked for spelling.
371              
372              
373             =head1 PREREQUISITES
374              
375             This policy will disable itself if L<File::Which|File::Which> is not
376             available.
377              
378              
379             =head1 CREDITS
380              
381             Initial development of this policy was supported by a grant from the
382             Perl Foundation.
383              
384              
385             =head1 AUTHOR
386              
387             Chris Dolan <cdolan@cpan.org>
388              
389              
390             =head1 COPYRIGHT
391              
392             Copyright (c) 2007-2023 Chris Dolan. Many rights reserved.
393              
394             This program is free software; you can redistribute it and/or modify
395             it under the same terms as Perl itself. The full text of this license
396             can be found in the LICENSE file included with this module
397              
398             =cut
399              
400             # Local Variables:
401             # mode: cperl
402             # cperl-indent-level: 4
403             # fill-column: 78
404             # indent-tabs-mode: nil
405             # c-indentation-style: bsd
406             # End:
407             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :