File Coverage

lib/Comment/Spell/Check.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1 2     2   16787 use 5.006;
  2         5  
  2         68  
2 2     2   7 use strict;
  2         2  
  2         60  
3 2     2   16 use warnings;
  2         3  
  2         115  
4              
5             package Comment::Spell::Check;
6              
7             our $VERSION = '0.001002';
8              
9             # ABSTRACT: Check words from Comment::Spell vs a system spell checker.
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 2     2   1121 use Moo qw( has extends around );
  2         30532  
  2         15  
14 2     2   2831 use Carp qw( croak carp );
  2         4  
  2         157  
15 2     2   1778 use Devel::CheckBin qw( can_run );
  0            
  0            
16             use IPC::Run qw( run timeout );
17             use Text::Wrap qw( wrap );
18             use File::Spec;
19              
20             extends 'Comment::Spell';
21              
22             has 'spell_command' => ( is => 'ro', lazy => 1, builder => '_build_spell_command' );
23             has 'spell_command_exec' => ( is => 'ro', lazy => 1, builder => '_build_spell_command_exec' );
24             has 'spell_command_args' => ( is => 'ro', lazy => 1, default => sub { [] } );
25             has '_spell_command_base_args' => ( is => 'ro', lazy => 1, builder => '_build_spell_command_base_args' );
26             has '_spell_command_all_args' => ( is => 'ro', lazy => 1, builder => '_build_spell_command_all_args' );
27              
28             my $arg_defaults = {
29             'spell' => [],
30             'aspell' => [ 'list', '-l', 'en', '-p', File::Spec->devnull, ],
31             'ispell' => [ '-l', ],
32             'hunspell' => [ '-l', ],
33             };
34              
35             sub _run_spell {
36             my ( $command, $words ) = @_;
37             my @badwords;
38             local $@ = undef;
39             my $ok = eval {
40             my ( $results, $errors );
41             run $command, \$words, \$results, \$errors, timeout(10);
42             @badwords = split /\n/msx, $results;
43             croak 'spellchecker had errors: ' . $errors if length $errors;
44             1;
45             };
46             chomp for @badwords;
47             return ( $ok, \@badwords, $@ );
48             }
49              
50             sub _can_spell {
51             my ($name) = @_;
52             return unless my $bin = can_run($name);
53             my ( $ok, $words, ) = _run_spell( [ $bin, @{ $arg_defaults->{$name} || [] } ], 'iamnotaword' );
54             return unless $ok;
55             return unless @{$words};
56             return unless 'iamnotaword' eq $words->[0];
57             return $bin;
58             }
59              
60             sub _build_spell_command_exec {
61             my @candidates = (qw( spell aspell ispell hunspell ));
62             for my $candidate (@candidates) {
63             return $candidate if _can_spell($candidate);
64             }
65             return croak <<"EOF";
66             Cant determine a spell checker automatically. Make sure one of: @candidates are installed or configure manually.
67             EOF
68             }
69              
70             sub _build_spell_command_base_args {
71             my ($self) = @_;
72             my $cmd = $self->spell_command_exec;
73             return ( $arg_defaults->{$cmd} || [] );
74             }
75              
76             sub _build_spell_command_all_args {
77             my ($self) = @_;
78             return [ @{ $self->_spell_command_base_args }, @{ $self->spell_command_args } ];
79             }
80              
81             sub _build_spell_command {
82             my ($self) = @_;
83             return [ can_run( $self->spell_command_exec ), @{ $self->_spell_command_all_args } ];
84             }
85              
86             sub _spell_text {
87             my ( $self, $text ) = @_;
88             my @command = @{ $self->spell_command };
89             my ( $ok, $words, $err ) = _run_spell( \@command, $text );
90             if ( not $ok ) {
91             carp $err;
92             }
93             return @{$words};
94             }
95              
96             around 'parse_from_document' => sub {
97             my ( $orig, $self, $document, @rest ) = @_;
98             local $self->{fails} = []; ## no critic (Variables::ProhibitLocalVars)
99             my %counts;
100             local $self->{counts} = \%counts; ## no critic (Variables::ProhibitLocalVars)
101              
102             $document->index_locations;
103             $self->$orig( $document, @rest );
104              
105             if ( keys %counts ) {
106              
107             # Invert k => v to v => [ k ]
108             my %values;
109             push @{ $values{ $counts{$_} } }, $_ for keys %counts;
110              
111             my $labelformat = q[%6s: ];
112             my $indent = q[ ] x 10;
113              
114             $self->_print_output( qq[\nAll incorrect words, by number of occurrences:\n] . join qq[\n],
115             map { wrap( ( sprintf $labelformat, $_ ), $indent, join q[, ], sort @{ $values{$_} } ) }
116             sort { $a <=> $b } keys %values );
117             $self->_flush_output;
118             }
119             return { fails => $self->{fails}, counts => $self->{counts} };
120             };
121              
122             sub _handle_comment {
123             my ( $self, $comment ) = @_;
124             my $comment_text = $self->stopwords->strip_stopwords( $self->_comment_text($comment) );
125             my (@badwords) = $self->_spell_text($comment_text);
126             return unless @badwords;
127             my %counts;
128             $counts{$_}++ for @badwords;
129             $self->{counts}->{$_}++ for @badwords;
130             my $fail = {
131             line => $comment->line_number,
132             counts => \%counts,
133             };
134             push @{ $self->{fails} }, $fail;
135             my $label = sprintf q[line %6s: ], q[#] . $comment->line_number;
136             my $indent = q[ ] x 13;
137             local $Text::Wrap::huge = 'overflow'; ## no critic (Variables::ProhibitPackageVars)
138             my @printwords;
139              
140             for my $key ( sort keys %counts ) {
141             if ( $counts{$key} > 1 ) {
142             push @printwords, $key . '(x' . $counts{$key} . ')';
143             next;
144             }
145             push @printwords, $key;
146             }
147             $self->_print_output( wrap( $label, $indent, join q[ ], @printwords ) );
148             $self->_print_output(qq[\n]);
149             return;
150             }
151              
152             no Moo;
153              
154             1;
155              
156             __END__