line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
1
|
|
|
1
|
|
87164
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
26
|
|
2
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
21
|
|
3
|
1
|
|
|
1
|
|
5
|
use utf8; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
7
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
package Pod::Weaver::Plugin::EnsureUniqueSections; |
6
|
|
|
|
|
|
|
$Pod::Weaver::Plugin::EnsureUniqueSections::VERSION = '0.163250'; |
7
|
1
|
|
|
1
|
|
43
|
use Moose; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
4
|
|
8
|
1
|
|
|
1
|
|
5704
|
use MooseX::Has::Sugar; |
|
1
|
|
|
|
|
592
|
|
|
1
|
|
|
|
|
4
|
|
9
|
1
|
|
|
1
|
|
93
|
use Moose::Autobox 0.10; |
|
1
|
|
|
|
|
20
|
|
|
1
|
|
|
|
|
6
|
|
10
|
1
|
|
|
1
|
|
820
|
use Text::Trim; |
|
1
|
|
|
|
|
435
|
|
|
1
|
|
|
|
|
52
|
|
11
|
|
|
|
|
|
|
|
12
|
1
|
|
|
1
|
|
237
|
use Lingua::EN::Inflect::Number qw(to_S); |
|
1
|
|
|
|
|
22827
|
|
|
1
|
|
|
|
|
5
|
|
13
|
1
|
|
|
1
|
|
162
|
use Carp; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
545
|
|
14
|
|
|
|
|
|
|
with 'Pod::Weaver::Role::Finalizer'; |
15
|
|
|
|
|
|
|
with 'Pod::Weaver::Role::Preparer'; |
16
|
|
|
|
|
|
|
# ABSTRACT: Ensure that POD has no duplicate section headers. |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
has strict => ( |
20
|
|
|
|
|
|
|
ro, lazy, |
21
|
|
|
|
|
|
|
isa => 'Bool', |
22
|
|
|
|
|
|
|
default => sub { 0 }, |
23
|
|
|
|
|
|
|
); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
sub _header_key { |
26
|
40
|
|
|
40
|
|
77
|
my ($self, $text) = @_; |
27
|
40
|
100
|
|
|
|
1375
|
if (!$self->strict) { |
28
|
|
|
|
|
|
|
# Replace all non-words with a single space |
29
|
36
|
|
|
|
|
101
|
$text =~ s{\W+}{ }xsmg; |
30
|
|
|
|
|
|
|
# Trim leading and trailing whitespace |
31
|
36
|
|
|
|
|
101
|
$text = trim $text; |
32
|
|
|
|
|
|
|
# All to uppercase |
33
|
36
|
|
|
|
|
614
|
$text = uc $text; |
34
|
|
|
|
|
|
|
# Reorder "AND" lists and singularize nouns |
35
|
|
|
|
|
|
|
$text = $text |
36
|
|
|
|
|
|
|
->split(qr{ AND }i) |
37
|
43
|
100
|
|
43
|
|
4739
|
->map(sub { m{\W} ? $_ : to_S $_; }) |
38
|
36
|
|
|
|
|
181
|
->sort->join(' AND '); |
39
|
|
|
|
|
|
|
} |
40
|
40
|
|
|
|
|
22462
|
return $text; |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub prepare_input { |
45
|
6
|
|
|
6
|
1
|
921337
|
my $self = shift; |
46
|
|
|
|
|
|
|
# Put EnsureUniqueSections plugins at the end |
47
|
6
|
|
|
|
|
160
|
my $plugins = $self->weaver->plugins; |
48
|
6
|
|
|
|
|
178
|
@$plugins = ((grep { $_ != $self } @$plugins), $self); |
|
90
|
|
|
|
|
167
|
|
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
sub finalize_document { |
53
|
6
|
|
|
6
|
1
|
105532
|
my ($self, $document) = @_; |
54
|
|
|
|
|
|
|
my $headers = $document->children |
55
|
40
|
50
|
|
40
|
|
1353
|
->grep(sub{ $_->can( 'command' ) and $_->command eq 'head1' }) |
56
|
6
|
|
|
40
|
|
136
|
->map(sub{ $_->content }); |
|
40
|
|
|
|
|
1102
|
|
57
|
6
|
|
|
|
|
58
|
my %header_group; |
58
|
6
|
|
|
|
|
17
|
for my $h (@$headers) { |
59
|
40
|
|
|
|
|
88
|
push @{$header_group{$self->_header_key($h)}}, $h; |
|
40
|
|
|
|
|
100
|
|
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
my $duplicate_headers = [ keys %header_group ] |
63
|
36
|
100
|
|
36
|
|
121
|
->map(sub{ @{$header_group{$_}} > 1 ? $header_group{$_}->head : () }) |
|
36
|
|
|
|
|
107
|
|
64
|
6
|
|
|
|
|
55
|
->sort; |
65
|
6
|
100
|
|
|
|
80
|
if (@$duplicate_headers > 0) { |
66
|
4
|
|
|
|
|
9
|
my $pod_string = ""; |
67
|
4
|
|
|
|
|
10
|
for my $h (@$duplicate_headers) { |
68
|
4
|
|
|
|
|
7
|
for my $node (@{ $document->children->grep( |
69
|
|
|
|
|
|
|
sub { |
70
|
30
|
50
|
33
|
30
|
|
1640
|
$_->can('command') && $_->command eq 'head1' && |
71
|
|
|
|
|
|
|
$_->content eq $h |
72
|
4
|
|
|
|
|
115
|
}) }) { |
73
|
7
|
|
|
|
|
1126
|
$pod_string .= $node->as_pod_string; |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
} |
76
|
4
|
|
|
|
|
1099
|
$self->log_debug(["POD of duplicated headers:\n\n%s", $pod_string]); |
77
|
4
|
|
|
|
|
361
|
my $message = "Error: The following headers appear multiple times: '" . $duplicate_headers->join(q{', '}) . q{'}; |
78
|
4
|
|
|
|
|
45
|
$self->log_fatal($message); |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
1; # Magic true value required at end of module |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
__END__ |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=pod |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=head1 NAME |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
Pod::Weaver::Plugin::EnsureUniqueSections - Ensure that POD has no duplicate section headers. |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=head1 VERSION |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
version 0.163250 |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=head1 SYNOPSIS |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
In F<weaver.ini> |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
[-EnsureUniqueSections] |
101
|
|
|
|
|
|
|
strict = 0 ; The default |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=head1 DESCRIPTION |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
This plugin simply ensures that the POD after weaving has no duplicate |
106
|
|
|
|
|
|
|
top-level section headers. This can help you if you are converting |
107
|
|
|
|
|
|
|
from writing all your own POD to generating it with L<Pod::Weaver>. If |
108
|
|
|
|
|
|
|
you begin generating a section with L<Pod::Weaver> but you forget to |
109
|
|
|
|
|
|
|
delete the manually written section of the same name, this plugin will |
110
|
|
|
|
|
|
|
warn you. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
By default, this module does some tricks to detect similar headers, |
113
|
|
|
|
|
|
|
such as C<AUTHOR> and C<AUTHORS>. You can turn this off by setting |
114
|
|
|
|
|
|
|
C<strict = 1> in F<weaver.ini>, in which case only I<exactly identical> |
115
|
|
|
|
|
|
|
headers will be considered duplicates of each other. |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head2 DIAGNOSTIC MESSAGES |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
If any similar (or identical if C<strict> is 1) section headers are |
120
|
|
|
|
|
|
|
found, all of their names will be listed on STDERR. Generally, you |
121
|
|
|
|
|
|
|
should take this list of modules and remove each from your POD. Then |
122
|
|
|
|
|
|
|
you should ensure that the sections generated by L<Pod::Weaver> are |
123
|
|
|
|
|
|
|
suitable substitutes for those sections. In the case of similar names, |
124
|
|
|
|
|
|
|
only the first instance in each set of similar names will be listed. |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=head2 strict |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
If set to true (1), section headers will only be considered duplicates |
131
|
|
|
|
|
|
|
if they match exactly. If false (the default), certain similar section |
132
|
|
|
|
|
|
|
headers will be considered equivalent. The following similarities are |
133
|
|
|
|
|
|
|
considered (more may be added later): |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=over 4 |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=item All whitespace and punctuation are equivalant |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
For example, the following would all be considered duplicates of each |
140
|
|
|
|
|
|
|
other: C< SEE ALSO>, C<SEE ALSO>, C<SEE,ALSO:>. |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=item Case-insensitive |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
For example, C<Name> and C<NAME> would be considered duplicates. |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=item Sets of words separated by "AND". |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
For example, "COPYRIGHT AND LICENSE" would be considered a duplicate |
149
|
|
|
|
|
|
|
of "LICENSE AND COPYRIGHT". |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=item Plurals |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
A plural noun is considered equivalent to its singular. For example, |
154
|
|
|
|
|
|
|
"AUTHOR" and "AUTHORS" are the same section. A section header |
155
|
|
|
|
|
|
|
consisting of multiple words, such as "DISCLAIMER OF WARRANTY", is not |
156
|
|
|
|
|
|
|
affected by this rule. |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
This rule uses L<Lingua::EN::Inflect::Number> to interconvert between |
159
|
|
|
|
|
|
|
singular and plural forms. Hopefully you don't need to make a section |
160
|
|
|
|
|
|
|
called C<OCTOPI>. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=back |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
Note that these rules apply recursively, so C<Authors; and |
165
|
|
|
|
|
|
|
Contributors> would be a duplicate of C< CONTRIBUTORS AND AUTHOR>. |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=head1 METHODS |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=head2 prepare_input |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
This method modifies the weaver object by moving EnsureUniqueSections |
172
|
|
|
|
|
|
|
to the end of the weaver's plugin list to ensure that it gets to look |
173
|
|
|
|
|
|
|
at the final woven POD. |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
THIS IS PURE EVIL. This is a hack to ensure that this plugin gets "the |
176
|
|
|
|
|
|
|
last word". Obviously if all plugins used this it would be total |
177
|
|
|
|
|
|
|
chaos. I welcome alternative suggestions. The main issue is that when |
178
|
|
|
|
|
|
|
other Finalizers, such as Section::Leftovers (which happens to be the |
179
|
|
|
|
|
|
|
most likely plugin to create duplicate sections), produce sections, |
180
|
|
|
|
|
|
|
this plugin will only see those sections if it runs after those |
181
|
|
|
|
|
|
|
Finalizers. Hence the need to be the last plugin on the list. |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=head2 finalize_document |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
This method checks the document for duplicate headers, and throws an |
186
|
|
|
|
|
|
|
error if any are found. If no duplicates are found, it simply does |
187
|
|
|
|
|
|
|
nothing. It does not modify the POD in any way. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head1 BUGS AND LIMITATIONS |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=head2 Should also be available as a L<Dist::Zilla> testing plugin |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
I would like to convert this to a L<Dist::Zilla> testing plugin, so that |
194
|
|
|
|
|
|
|
you can use it without enabling L<Pod::Weaver> if you don't want to, |
195
|
|
|
|
|
|
|
but I haven't yet figured out how to find all files in a dist with POD |
196
|
|
|
|
|
|
|
and extract all their headers. If anyone knows, please tell me. |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head2 No recursive duplicate checks |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
This module only checks for duplicates in top-level headers (i.e. |
201
|
|
|
|
|
|
|
C<head1>). It could be extended to check the C<head2> elements within |
202
|
|
|
|
|
|
|
each C<head1> section and so on, but generally L<Pod::Weaver> is not |
203
|
|
|
|
|
|
|
called upon to generate subsections, so you are unlikely to end up |
204
|
|
|
|
|
|
|
with duplicates at any level other than the first. However, if there |
205
|
|
|
|
|
|
|
is demand for recursive duplicate detection, I will add it. |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Please report any bugs or feature requests to |
208
|
|
|
|
|
|
|
C<rct+perlbug@thompsonclan.org>. |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=head1 SEE ALSO |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=over 4 |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=item * |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
L<Pod::Weaver> - The module that this is a plugin for. |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=item * |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
L<Lingua::EN::Inflect::Number> - Used to determine the singular forms of plural nouns. |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=back |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
=head1 INSTALLATION |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
See perlmodinstall for information and options on installing Perl modules. |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
=head1 AUTHOR |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
Ryan C. Thompson <rct@thompsonclan.org> |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
This software is copyright (c) 2010 by Ryan C. Thompson. |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
237
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
=head1 DISCLAIMER OF WARRANTY |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY |
242
|
|
|
|
|
|
|
FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT |
243
|
|
|
|
|
|
|
WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER |
244
|
|
|
|
|
|
|
PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, |
245
|
|
|
|
|
|
|
EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE |
246
|
|
|
|
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
247
|
|
|
|
|
|
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE |
248
|
|
|
|
|
|
|
SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME |
249
|
|
|
|
|
|
|
THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION. |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
252
|
|
|
|
|
|
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR |
253
|
|
|
|
|
|
|
REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE |
254
|
|
|
|
|
|
|
TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR |
255
|
|
|
|
|
|
|
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE |
256
|
|
|
|
|
|
|
SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING |
257
|
|
|
|
|
|
|
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A |
258
|
|
|
|
|
|
|
FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF |
259
|
|
|
|
|
|
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
260
|
|
|
|
|
|
|
DAMAGES. |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=cut |