line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Test::DocClaims::Lines; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# Copyright (c) Scott E. Lee |
4
|
|
|
|
|
|
|
|
5
|
11
|
|
|
11
|
|
552632
|
use 5.008009; |
|
11
|
|
|
|
|
99
|
|
6
|
11
|
|
|
11
|
|
48
|
use strict; |
|
11
|
|
|
|
|
30
|
|
|
11
|
|
|
|
|
265
|
|
7
|
11
|
|
|
11
|
|
44
|
use warnings; |
|
11
|
|
|
|
|
15
|
|
|
11
|
|
|
|
|
315
|
|
8
|
11
|
|
|
11
|
|
55
|
use Carp; |
|
11
|
|
|
|
|
19
|
|
|
11
|
|
|
|
|
536
|
|
9
|
|
|
|
|
|
|
|
10
|
11
|
|
|
11
|
|
3530
|
use Test::DocClaims::Line; |
|
11
|
|
|
|
|
22
|
|
|
11
|
|
|
|
|
13430
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# Tell croak to skip over calls from here. |
13
|
|
|
|
|
|
|
our @CARP_NOT = qw< Test::DocClaims >; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
# Keys in the blessed hash |
16
|
|
|
|
|
|
|
# {lines} array of Test::DocClaims::Line objects |
17
|
|
|
|
|
|
|
# {current} the current index into {lines} |
18
|
|
|
|
|
|
|
# {paths} list of paths and/or globs used to read the lines |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head1 NAME |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
Test::DocClaims::Lines - Represent lines form one of more files |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=head1 SYNOPSIS |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
use Test::DocClaims::Lines; |
27
|
|
|
|
|
|
|
my $lines = Test::DocClaims::Lines->new("t/Foo*.t"); |
28
|
|
|
|
|
|
|
my %files; |
29
|
|
|
|
|
|
|
while ( !$lines->is_eof ) { |
30
|
|
|
|
|
|
|
my $line = $lines->current_line; |
31
|
|
|
|
|
|
|
$files{ $line->path }[ $line->lnum - 1 ] = $line->text; |
32
|
|
|
|
|
|
|
$lines->advance_line; |
33
|
|
|
|
|
|
|
} |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=head1 DESCRIPTION |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
This holds a collection of lines from one or more files. |
38
|
|
|
|
|
|
|
The file path and line number of each line is recorded as well as |
39
|
|
|
|
|
|
|
other attributes of both the file and the individual lines. |
40
|
|
|
|
|
|
|
For example, it records whether a file supports POD documentation |
41
|
|
|
|
|
|
|
and whether each line is POD documentation or not. |
42
|
|
|
|
|
|
|
Each line in the list is represented as a Test::DocClaims::Line object. |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
There is a concept of current line. |
45
|
|
|
|
|
|
|
This can be used to step through the lines sequentially. |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=head2 new I |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
The I argument specifies a list of one or more files. |
52
|
|
|
|
|
|
|
It can be one of: |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
- a string which is the path to a file or a wildcard which is |
55
|
|
|
|
|
|
|
expanded by the glob built-in function. |
56
|
|
|
|
|
|
|
- a ref to a hash with these keys: |
57
|
|
|
|
|
|
|
- path: path or wildcard (required) |
58
|
|
|
|
|
|
|
- has_pod: true if the file can have POD (optional) |
59
|
|
|
|
|
|
|
- a ref to an array, where each element is a path, wildcard or hash |
60
|
|
|
|
|
|
|
as above |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
If a list of files is given, those files are read in order and the |
63
|
|
|
|
|
|
|
lines in each are concatenated. |
64
|
|
|
|
|
|
|
If a wildcard expands to more than one file they are read in the order |
65
|
|
|
|
|
|
|
returned by the glob built-in. |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=cut |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub new { |
70
|
151
|
|
|
151
|
1
|
1346
|
my $class = shift; |
71
|
151
|
|
|
|
|
182
|
my $file_spec = shift; |
72
|
151
|
50
|
|
|
|
251
|
croak "missing arg to Test::DocClaims::Line->new" unless $file_spec; |
73
|
151
|
|
33
|
|
|
448
|
my $self = bless {}, ref($class) || $class; |
74
|
151
|
|
|
|
|
358
|
$self->{lines} = []; |
75
|
151
|
|
|
|
|
217
|
$self->{current} = 0; |
76
|
151
|
|
|
|
|
199
|
$self->{paths} = []; |
77
|
151
|
|
|
|
|
305
|
foreach my $attrs ( $self->_file_spec_to_list($file_spec) ) { |
78
|
203
|
|
|
|
|
335
|
$self->_add_file($attrs); |
79
|
|
|
|
|
|
|
} |
80
|
151
|
|
|
|
|
324
|
return $self; |
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=head1 ACCESSORS |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=head2 is_eof |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
This returns true if the end of the lines has been reached. |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=cut |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub is_eof { |
92
|
12458
|
|
|
12458
|
1
|
13567
|
my $self = shift; |
93
|
12458
|
|
|
|
|
12941
|
return $self->{current} >= scalar( @{ $self->{lines} } ); |
|
12458
|
|
|
|
|
23252
|
|
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=head2 advance_line |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
This advances to the next line and returns the Test::DocClaims::Line |
99
|
|
|
|
|
|
|
object for that line. |
100
|
|
|
|
|
|
|
If there is no next line, undef is returned. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=cut |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
sub advance_line { |
105
|
5311
|
|
|
5311
|
1
|
5767
|
my $self = shift; |
106
|
5311
|
|
|
|
|
5622
|
$self->{current}++; |
107
|
5311
|
|
|
|
|
6241
|
return $self->current_line; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=head2 current_line |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Return the current line, a Test::DocClaims::Line object. |
113
|
|
|
|
|
|
|
If there is no current line because the end has been reached, undef is |
114
|
|
|
|
|
|
|
returned. |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=cut |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub current_line { |
119
|
10640
|
|
|
10640
|
1
|
10962
|
my $self = shift; |
120
|
10640
|
100
|
|
|
|
12310
|
return undef if $self->is_eof; |
121
|
10494
|
|
|
|
|
19807
|
return $self->{lines}[ $self->{current} ]; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=head2 paths |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
Return a list of strings for the paths and/or globs used to read the file |
127
|
|
|
|
|
|
|
or files. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=cut |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub paths { |
132
|
1
|
|
|
1
|
1
|
3
|
my $self = shift; |
133
|
1
|
|
|
|
|
2
|
return @{ $self->{paths} }; |
|
1
|
|
|
|
|
5
|
|
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
# Convert a file spec arg to a list of attribute hashes representing the |
137
|
|
|
|
|
|
|
# files. |
138
|
|
|
|
|
|
|
sub _file_spec_to_list { |
139
|
151
|
|
|
151
|
|
183
|
my $self = shift; |
140
|
151
|
|
|
|
|
174
|
my $arg = shift; |
141
|
151
|
100
|
|
|
|
330
|
$arg = [$arg] unless ref $arg eq "ARRAY"; |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# Expand wildcards to a list of paths (or hashes), putting the results |
144
|
|
|
|
|
|
|
# into @specs. |
145
|
151
|
|
|
|
|
183
|
my @specs; |
146
|
151
|
|
|
|
|
230
|
foreach my $item (@$arg) { |
147
|
166
|
100
|
|
|
|
268
|
if ( ref $item eq "HASH" ) { |
148
|
|
|
|
|
|
|
croak "file spec is hash, but it has no 'path' key" |
149
|
29
|
50
|
|
|
|
67
|
unless length $item->{path}; |
150
|
29
|
|
|
|
|
32
|
push @{ $self->{paths} }, "$item->{path}"; |
|
29
|
|
|
|
|
65
|
|
151
|
29
|
|
|
|
|
59
|
my @list = _glob( $item->{path} ); |
152
|
29
|
|
|
|
|
1441
|
@list = sort @list; |
153
|
29
|
50
|
|
|
|
59
|
croak "no such file ($item->{path})" unless @list; |
154
|
29
|
|
|
|
|
45
|
foreach my $path (@list) { |
155
|
37
|
|
|
|
|
131
|
push @specs, { %$item, path => $path }; |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
} else { |
158
|
137
|
|
|
|
|
143
|
push @{ $self->{paths} }, "$item"; |
|
137
|
|
|
|
|
302
|
|
159
|
137
|
|
|
|
|
249
|
my @list = _glob($item); |
160
|
137
|
|
|
|
|
10344
|
@list = sort @list; |
161
|
137
|
50
|
|
|
|
264
|
croak "no such file ($item)" unless @list; |
162
|
137
|
|
|
|
|
289
|
push @specs, @list; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
# Convert each item in the list to a hash if it isn't already and fill |
167
|
|
|
|
|
|
|
# in any missing attributes with default values. |
168
|
151
|
|
|
|
|
257
|
foreach my $item (@specs) { |
169
|
203
|
100
|
|
|
|
338
|
if ( ref $item eq "HASH" ) { |
170
|
37
|
|
|
|
|
72
|
my %default = $self->_attrs_of_file( $item->{path} ); |
171
|
37
|
|
|
|
|
71
|
foreach my $key ( keys %default ) { |
172
|
74
|
100
|
|
|
|
163
|
$item->{$key} = $default{$key} unless defined $item->{$key}; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
} else { |
175
|
166
|
|
|
|
|
307
|
$item = { path => $item, $self->_attrs_of_file($item) }; |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
} |
178
|
151
|
|
|
|
|
310
|
return @specs; |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# This wrapper for the glob function can be overridden at run time (by the |
182
|
|
|
|
|
|
|
# TestTester module), where the system glob can only be overridden at |
183
|
|
|
|
|
|
|
# compile time. |
184
|
|
|
|
|
|
|
sub _glob { |
185
|
17
|
|
|
17
|
|
565
|
return glob( $_[0] ); |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# Each attribute hash has at least these keys: |
189
|
|
|
|
|
|
|
# path the path of the file |
190
|
|
|
|
|
|
|
# has_pod true if it should be parsed as POD |
191
|
|
|
|
|
|
|
# white true if amount of white space at beginning of lines is preserved |
192
|
|
|
|
|
|
|
# TODO remove white attribute |
193
|
|
|
|
|
|
|
sub _attrs_of_file { |
194
|
203
|
|
|
203
|
|
242
|
my $self = shift; |
195
|
203
|
|
|
|
|
255
|
my $path = shift; |
196
|
203
|
|
|
|
|
365
|
my %attrs = ( |
197
|
|
|
|
|
|
|
has_pod => 0, |
198
|
|
|
|
|
|
|
white => 0, |
199
|
|
|
|
|
|
|
); |
200
|
203
|
100
|
|
|
|
695
|
if ( $path =~ /\.p[lm]$/ ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
201
|
70
|
|
|
|
|
100
|
$attrs{has_pod} = 1; |
202
|
|
|
|
|
|
|
} elsif ( $path =~ /\.pod$/ ) { |
203
|
1
|
|
|
|
|
2
|
$attrs{has_pod} = 1; |
204
|
|
|
|
|
|
|
} elsif ( $path =~ /\.t$/ ) { |
205
|
128
|
|
|
|
|
180
|
$attrs{has_pod} = 1; |
206
|
|
|
|
|
|
|
} |
207
|
203
|
|
|
|
|
723
|
return %attrs; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub _add_file { |
211
|
203
|
|
|
203
|
|
226
|
my $self = shift; |
212
|
203
|
|
|
|
|
221
|
my $attrs = shift; |
213
|
203
|
|
|
|
|
378
|
my $lines = _read_file( $attrs->{path} ); |
214
|
203
|
|
|
|
|
1211
|
my $lnum = 0; |
215
|
203
|
|
|
|
|
289
|
my $doc_mode = !$attrs->{has_pod}; |
216
|
203
|
|
|
|
|
229
|
my $code = undef; |
217
|
203
|
|
|
|
|
261
|
my $todo = undef; |
218
|
203
|
|
|
|
|
223
|
my $in_data = 0; # ignore TestTester files in __DATA__ section |
219
|
|
|
|
|
|
|
|
220
|
203
|
|
|
|
|
281
|
foreach my $text (@$lines) { |
221
|
5488
|
100
|
|
|
|
9117
|
$in_data = 1 if $text =~ /^__(END|DATA)__$/; |
222
|
5488
|
100
|
100
|
|
|
7914
|
last if $in_data && $text =~ /^FILE:<.*>-/; |
223
|
5478
|
|
|
|
|
10069
|
my %hash = ( orig => $text, lnum => ++$lnum ); |
224
|
5478
|
|
|
|
|
5653
|
my $this_line_doc; |
225
|
5478
|
100
|
|
|
|
7916
|
if ( $attrs->{has_pod} ) { |
226
|
5477
|
100
|
|
|
|
8820
|
if ( $text =~ /^=([a-zA-Z]\S*)(\s+(.*))?\s*$/ ) { |
227
|
615
|
|
|
|
|
1462
|
my ( $cmd, $cmd_text ) = ( $1, $2 ); |
228
|
615
|
|
|
|
|
758
|
$hash{is_doc} = 1; |
229
|
615
|
|
|
|
|
669
|
$doc_mode = 1; |
230
|
615
|
100
|
|
|
|
1847
|
if ( $cmd eq "pod" ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
231
|
34
|
|
|
|
|
44
|
$this_line_doc = 0; |
232
|
|
|
|
|
|
|
} elsif ( $cmd =~ /^cut/ ) { |
233
|
143
|
|
|
|
|
223
|
my ( $format, $args ) = _parse_pod_command($cmd_text); |
234
|
143
|
|
|
|
|
181
|
$this_line_doc = 0; |
235
|
143
|
|
|
|
|
204
|
$doc_mode = 0; |
236
|
|
|
|
|
|
|
} elsif ( $cmd =~ /^begin/ ) { |
237
|
28
|
|
|
|
|
59
|
my ( $format, $args ) = _parse_pod_command($cmd_text); |
238
|
28
|
50
|
|
|
|
72
|
if ( $format eq "DC_CODE" ) { |
239
|
28
|
|
|
|
|
38
|
$this_line_doc = 0; |
240
|
28
|
|
|
|
|
40
|
$code = $args; |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
} elsif ( $cmd =~ /^end/ ) { |
243
|
28
|
|
|
|
|
45
|
my ( $format, $args ) = _parse_pod_command($cmd_text); |
244
|
28
|
50
|
|
|
|
73
|
if ( $format eq "DC_CODE" ) { |
245
|
28
|
|
|
|
|
33
|
$this_line_doc = 0; |
246
|
28
|
|
|
|
|
49
|
$code = undef; |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
} elsif ( $cmd =~ /^for/ ) { |
249
|
4
|
|
|
|
|
17
|
my ( $format, $args ) = _parse_pod_command($cmd_text); |
250
|
4
|
50
|
|
|
|
13
|
if ( $format eq "DC_TODO" ) { |
251
|
4
|
|
|
|
|
6
|
$this_line_doc = 0; |
252
|
4
|
|
|
|
|
7
|
$todo = $args; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
} |
257
|
5478
|
100
|
|
|
|
7224
|
if ( !defined $this_line_doc ) { |
258
|
5241
|
100
|
100
|
|
|
10997
|
$this_line_doc = 1 if $code || $doc_mode; |
259
|
|
|
|
|
|
|
} |
260
|
5478
|
100
|
|
|
|
7815
|
$hash{is_doc} = $this_line_doc ? 1 : 0; |
261
|
5478
|
|
|
|
|
6195
|
$hash{has_pod} = $attrs->{has_pod}; |
262
|
5478
|
|
|
|
|
5778
|
$hash{code} = $code; |
263
|
5478
|
|
|
|
|
5548
|
$hash{todo} = $todo; |
264
|
5478
|
|
|
|
|
5477
|
$todo = undef; |
265
|
5478
|
|
|
|
|
14647
|
$text =~ s/\s+$//; # remove CRLF, NL and trailing white space |
266
|
5478
|
50
|
|
|
|
11131
|
$text =~ s/^\s+/ / if !$attrs->{white}; |
267
|
5478
|
|
|
|
|
7608
|
$hash{text} = $text; |
268
|
5478
|
|
|
|
|
5965
|
$hash{file} = $attrs; |
269
|
5478
|
|
|
|
|
5327
|
push @{ $self->{lines} }, Test::DocClaims::Line->new(%hash); |
|
5478
|
|
|
|
|
14956
|
|
270
|
|
|
|
|
|
|
} |
271
|
203
|
|
|
|
|
575
|
return $self; |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
sub _parse_pod_command { |
275
|
203
|
|
|
203
|
|
266
|
my $text = shift; |
276
|
203
|
|
|
|
|
262
|
my ( $format, %args ); |
277
|
203
|
100
|
|
|
|
508
|
if ( $text =~ /^\s*(\S+)(\s+(.*))?$/ ) { |
278
|
60
|
|
|
|
|
109
|
$format = $1; |
279
|
|
|
|
|
|
|
%args = |
280
|
15
|
100
|
|
|
|
46
|
map { /^(.+?)=(.*)$/ ? ( $1 => $2 ) : ( $1 => 1 ) } |
281
|
60
|
|
100
|
|
|
225
|
grep { length $_ } |
|
15
|
|
|
|
|
24
|
|
282
|
|
|
|
|
|
|
split " ", $3 || ""; |
283
|
|
|
|
|
|
|
} else { |
284
|
143
|
|
|
|
|
180
|
$format = ""; |
285
|
|
|
|
|
|
|
} |
286
|
203
|
|
|
|
|
419
|
return ( $format, \%args ); |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
sub _read_file { |
290
|
26
|
|
|
26
|
|
31
|
my $path = shift; |
291
|
26
|
|
|
|
|
33
|
my @lines; |
292
|
26
|
50
|
|
|
|
784
|
if ( open my $fh, "<", $path ) { |
293
|
26
|
|
|
|
|
2080
|
@lines = <$fh>; |
294
|
26
|
|
|
|
|
225
|
close $fh; |
295
|
|
|
|
|
|
|
} else { |
296
|
0
|
|
|
|
|
0
|
die "cannot read $path: $!\n"; |
297
|
|
|
|
|
|
|
} |
298
|
26
|
|
|
|
|
122
|
return \@lines; |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=head1 COPYRIGHT |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
Copyright (c) Scott E. Lee |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=cut |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
1; |
308
|
|
|
|
|
|
|
|