File Coverage

blib/lib/HTML/Spelling/Site/Whitelist.pm
Criterion Covered Total %
statement 101 118 85.5
branch 25 30 83.3
condition 11 17 64.7
subroutine 11 15 73.3
pod 5 5 100.0
total 153 185 82.7


line stmt bran cond sub pod time code
1             package HTML::Spelling::Site::Whitelist;
2             $HTML::Spelling::Site::Whitelist::VERSION = '0.10.1';
3 2     2   103874 use strict;
  2         16  
  2         60  
4 2     2   11 use warnings;
  2         4  
  2         52  
5 2     2   638 use autodie;
  2         14707  
  2         11  
6              
7 2     2   12497 use 5.014;
  2         13  
8              
9 2     2   483 use MooX (qw( late ));
  2         13950  
  2         17  
10              
11 2     2   222636 use Path::Tiny qw/ path /;
  2         14597  
  2         3729  
12              
13             has '_general_whitelist' => ( is => 'ro', default => sub { return []; } );
14             has '_records' => ( is => 'ro', default => sub { return []; } );
15             has '_general_hashref' => ( is => 'ro', default => sub { return +{}; } );
16             has '_per_file' => ( is => 'ro', default => sub { return +{}; } );
17             has '_was_parsed' => ( is => 'rw', default => '' );
18             has 'filename' => ( is => 'ro', isa => 'Str', required => 1 );
19              
20             sub check_word
21             {
22 0     0 1 0 my ( $self, $args ) = @_;
23              
24 0         0 my $filename = $args->{filename};
25 0         0 my $word = $args->{word};
26              
27             return (
28             exists( $self->_general_hashref->{$word} )
29 0   0     0 or exists( $self->_per_file->{$filename}->{$word} )
30             );
31             }
32              
33             sub parse
34             {
35 3     3 1 9 my ($self) = @_;
36              
37 3 50       21 if ( !$self->_was_parsed() )
38             {
39              
40 3         22 my $rec;
41 3         30 open my $fh, '<:encoding(utf8)', $self->filename;
42 3         30350 my $found_global = 0;
43 3         1942 while ( my $l = <$fh> )
44             {
45 29         127 chomp($l);
46              
47             # Whitespace or comment - skip.
48 29 100 66     241 if ( $l !~ /\S/ or ( $l =~ /\A\s*#/ ) )
    100          
49             {
50             # Do nothing.
51             }
52             elsif ( $l =~ s/\A====\s+// )
53             {
54 6 100       50 if ( $l =~ /\AGLOBAL:\s*\z/ )
    50          
55             {
56 2 50       10 if ( defined($rec) )
57             {
58 0         0 die "GLOBAL is not the first directive.";
59             }
60 2         13 $found_global = 1;
61             }
62             elsif ( $l =~ /\AIn:\s*(.*)/ )
63             {
64 4         37 my @filenames = split /\s*,\s*/, $1;
65              
66 4 100       16 if ( defined($rec) )
67             {
68 3         7 push @{ $self->_records }, $rec;
  3         10  
69             }
70              
71 4         11 my %found;
72 4         13 foreach my $fn (@filenames)
73             {
74 11 50       37 if ( exists $found{$fn} )
75             {
76 0         0 die
77             "Filename <<$fn>> appears twice in line <<=== In: $l>>";
78             }
79 11         27 $found{$fn} = 1;
80             }
81             $rec = {
82 4         21 'files' => [ sort { $a cmp $b } @filenames ],
  9         51  
83             'words' => [],
84             },
85             ;
86             }
87             else
88             {
89 0         0 die "Unknown directive <<==== $l>>!";
90             }
91             }
92             else
93             {
94 13 100       34 if ( defined($rec) )
95             {
96 7         18 push @{ $rec->{'words'} }, $l;
  7         75  
97             }
98             else
99             {
100 6 50       21 if ( !$found_global )
101             {
102 0         0 die "GLOBAL not found before first word.";
103             }
104 6         9 push @{ $self->_general_whitelist }, $l;
  6         67  
105             }
106             }
107             }
108 3 100       21 if ( defined $rec )
109             {
110 1         4 push @{ $self->_records }, $rec;
  1         6  
111             }
112 3         21 close($fh);
113              
114 3         2998 foreach my $w ( @{ $self->_general_whitelist } )
  3         27  
115             {
116 6         33 $self->_general_hashref->{$w} = 1;
117             }
118              
119 3         9 foreach my $rec ( @{ $self->_records } )
  3         22  
120             {
121 4         10 my @lists;
122 4         9 foreach my $fn ( @{ $rec->{files} } )
  4         10  
123             {
124 11   100     52 push @lists, ( $self->_per_file->{$fn} //= +{} );
125             }
126              
127 4         7 foreach my $w ( @{ $rec->{words} } )
  4         14  
128             {
129 7         14 foreach my $l (@lists)
130             {
131 20         54 $l->{$w} = 1;
132             }
133             }
134             }
135             }
136 3         19 $self->_was_parsed(1);
137              
138 3         10 return;
139             }
140              
141             sub _rec_sorter
142             {
143 32     32   1486 my ( $a_aref, $b_aref, $idx ) = @_;
144              
145             return (
146 32 100 66     180 ( @$a_aref == $idx ) ? ( ( @$a_aref == @$b_aref ) ? 0 : -1 )
    100          
    100          
147             : ( @$b_aref == $idx ) ? 1
148             : ( ( $a_aref->[$idx] cmp $b_aref->[$idx] )
149             || _rec_sorter( $a_aref, $b_aref, $idx + 1 ) )
150             );
151             }
152              
153             sub _sort_words
154             {
155 4     4   10 my $words_aref = shift;
156              
157 4         35 return [ sort { $a cmp $b } @$words_aref ];
  6         34  
158             }
159              
160             sub _rec_cmp
161             {
162 8     8   28 my ( $aa, $bb ) = @_;
163 8         21 return _rec_sorter( $aa->{files}, $bb->{files}, 0 );
164             }
165              
166             sub get_sorted_text
167             {
168 2     2 1 93 my ($self) = @_;
169              
170 2         11 $self->parse;
171              
172 2         5 my %_gen = map { $_ => 1 } @{ $self->_general_whitelist };
  6         35  
  2         9  
173              
174             my @sorted_records =
175 2         13 sort { _rec_cmp( $a, $b ) } @{ $self->_records };
  5         12  
  2         14  
176              
177 2         5 my @merged_records;
178             {
179 2         3 my $i = 0;
  2         6  
180 2         11 while ( $i < @sorted_records )
181             {
182 2         5 my $final_i = $i;
183 2   100     10 while (
184             $final_i < $#sorted_records
185             and _rec_cmp(
186             $sorted_records[$i], $sorted_records[ $final_i + 1 ]
187             ) == 0
188             )
189             {
190 2         8 ++$final_i;
191             }
192 2 100       10 if ( $i == $final_i )
193             {
194 1         3 push @merged_records, $sorted_records[$i];
195             }
196             else
197             {
198             my $rec = {
199             files => $sorted_records[$i]->{files},
200             words => [
201 1         5 map { @{ $_->{words} } }
  3         7  
  3         17  
202             @sorted_records[ $i .. $final_i ]
203             ],
204             };
205 1         4 push @merged_records, $rec;
206             }
207 2         7 $i = $final_i + 1;
208             }
209             }
210              
211 19         80 return join '', map { "$_\n" } (
212             "==== GLOBAL:",
213             '',
214 2         12 @{ _sort_words( [ keys %_gen ] ) },
215             (
216             map {
217 2         6 my %found;
  2         6  
218             (
219             '',
220 2         12 ( "==== In: " . join( ' , ', @{ $_->{files} } ) ),
221             '',
222             (
223             @{
224 2         7 _sort_words(
225             [
226             grep {
227             !exists( $_gen{$_} )
228 7   66     40 and !( $found{$_}++ )
229 2         6 } @{ $_->{words} }
  2         8  
230             ]
231             )
232             }
233             )
234             )
235             } @merged_records
236             )
237             );
238             }
239              
240             sub _get_fh
241             {
242 0     0     my ($self) = @_;
243              
244 0           return path( $self->filename );
245             }
246              
247             sub is_sorted
248             {
249 0     0 1   my ($self) = @_;
250              
251 0           $self->parse;
252              
253 0           return ( $self->_get_fh->slurp_utf8() eq $self->get_sorted_text );
254             }
255              
256             sub write_sorted_file
257             {
258 0     0 1   my ($self) = @_;
259              
260 0           $self->parse;
261              
262 0           $self->_get_fh->spew_utf8( $self->get_sorted_text );
263              
264 0           return;
265             }
266              
267             1;
268              
269             __END__
270              
271             =pod
272              
273             =encoding UTF-8
274              
275             =head1 NAME
276              
277             HTML::Spelling::Site::Whitelist - handles the whitelist file.
278              
279             =head1 VERSION
280              
281             version 0.10.1
282              
283             =head1 SYNOPSIS
284              
285             use HTML::Spelling::Site::Whitelist;
286              
287             my $obj = HTML::Spelling::Site::Whitelist->new(
288             {
289             filename => 'path/to/whitelist.txt',
290             }
291             );
292              
293             $obj->parse;
294              
295             if (! $obj->check_word('clover'))
296             {
297             # Do more spell checking.
298             }
299              
300             $obj->write_sorted_file;
301              
302             =head1 DESCRIPTION
303              
304             The instances of this class can be used to manage a whitelist of words to
305             spell check.
306              
307             =head1 METHODS
308              
309             =head2 my $obj = HTML::Spelling::Site::Checker->new({ filename => './path/to/whitelist.txt'});
310              
311             Initialises a new object. C<filename> is the path to the file.
312              
313             =head2 $whitelist->parse;
314              
315             For now you should call this method right after the object is created.
316              
317             =head2 $finder->check_word({filename => $filename, word => $word})
318              
319             Checks if the word $word in the file $filename is in the whitelist.
320              
321             =head2 $finder->write_sorted_file;
322              
323             Rewrites the file to be sorted and canonicalized.
324              
325             =head2 $finder->is_sorted();
326              
327             Checks if the file is properly sorted and canonicalized.
328              
329             =head2 $finder->get_sorted_text()
330              
331             Returns the sorted text of the whitelist.
332              
333             =head2 $finder->filename()
334              
335             Returns the filename.
336              
337             =head1 WHITELIST FORMAT
338              
339             The format of the whitelist file is:
340              
341             ==== GLOBAL:
342              
343             [Global whitelist with one word per line]
344              
345             ==== In: path1 , path2 , path3
346              
347             [one word per line whitelist for path1, path2 and path3]
348              
349             ==== In: path4
350              
351             [one word per line whitelist for path4]
352              
353             (B<NOTE> that the paths are a complete path to the file and not parsed for
354             wildcards or regular expression syntax.)
355              
356             Here's another example:
357              
358             L<https://bitbucket.org/shlomif/shlomi-fish-homepage/src/493302cc5f1d81584f6f21bbd64197048e185aa6/lib/hunspell/whitelist1.txt?at=default&fileviewer=file-view-default>
359              
360             You should keep the whitelist file canonicalised and sorted by using
361             write_sorted_file() and is_sorted() .
362              
363             =for :stopwords cpan testmatrix url bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
364              
365             =head1 SUPPORT
366              
367             =head2 Websites
368              
369             The following websites have more information about this module, and may be of help to you. As always,
370             in addition to those websites please use your favorite search engine to discover more resources.
371              
372             =over 4
373              
374             =item *
375              
376             MetaCPAN
377              
378             A modern, open-source CPAN search engine, useful to view POD in HTML format.
379              
380             L<https://metacpan.org/release/HTML-Spelling-Site>
381              
382             =item *
383              
384             RT: CPAN's Bug Tracker
385              
386             The RT ( Request Tracker ) website is the default bug/issue tracking system for CPAN.
387              
388             L<https://rt.cpan.org/Public/Dist/Display.html?Name=HTML-Spelling-Site>
389              
390             =item *
391              
392             CPANTS
393              
394             The CPANTS is a website that analyzes the Kwalitee ( code metrics ) of a distribution.
395              
396             L<http://cpants.cpanauthors.org/dist/HTML-Spelling-Site>
397              
398             =item *
399              
400             CPAN Testers
401              
402             The CPAN Testers is a network of smoke testers who run automated tests on uploaded CPAN distributions.
403              
404             L<http://www.cpantesters.org/distro/H/HTML-Spelling-Site>
405              
406             =item *
407              
408             CPAN Testers Matrix
409              
410             The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms.
411              
412             L<http://matrix.cpantesters.org/?dist=HTML-Spelling-Site>
413              
414             =item *
415              
416             CPAN Testers Dependencies
417              
418             The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution.
419              
420             L<http://deps.cpantesters.org/?module=HTML::Spelling::Site>
421              
422             =back
423              
424             =head2 Bugs / Feature Requests
425              
426             Please report any bugs or feature requests by email to C<bug-html-spelling-site at rt.cpan.org>, or through
427             the web interface at L<https://rt.cpan.org/Public/Bug/Report.html?Queue=HTML-Spelling-Site>. You will be automatically notified of any
428             progress on the request by the system.
429              
430             =head2 Source Code
431              
432             The code is open to the world, and available for you to hack on. Please feel free to browse it and play
433             with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull
434             from your repository :)
435              
436             L<https://github.com/shlomif/HTML-Spelling-Site>
437              
438             git clone https://github.com/shlomif/HTML-Spelling-Site.git
439              
440             =head1 AUTHOR
441              
442             Shlomi Fish <shlomif@cpan.org>
443              
444             =head1 BUGS
445              
446             Please report any bugs or feature requests on the bugtracker website
447             L<https://github.com/shlomif/html-spelling-site/issues>
448              
449             When submitting a bug or request, please include a test-file or a
450             patch to an existing test-file that illustrates the bug or desired
451             feature.
452              
453             =head1 COPYRIGHT AND LICENSE
454              
455             This software is Copyright (c) 2016 by Shlomi Fish.
456              
457             This is free software, licensed under:
458              
459             The MIT (X11) License
460              
461             =cut