File Coverage

blib/lib/App/Ack/File.pm
Criterion Covered Total %
statement 12 73 16.4
branch 0 36 0.0
condition 0 18 0.0
subroutine 4 13 30.7
pod 8 9 88.8
total 24 149 16.1


line stmt bran cond sub pod time code
1             package App::Ack::File;
2              
3 2     2   1915 use warnings;
  2         5  
  2         73  
4 2     2   10 use strict;
  2         4  
  2         39  
5              
6 2     2   10 use App::Ack ();
  2         3  
  2         47  
7 2     2   10 use File::Spec ();
  2         3  
  2         1742  
8              
9             =head1 NAME
10              
11             App::Ack::File
12              
13             =head1 DESCRIPTION
14              
15             Abstracts a file from the filesystem.
16              
17             =head1 METHODS
18              
19             =head2 new( $filename )
20              
21             Opens the file specified by I<$filename> and returns a filehandle and
22             a flag that says whether it could be binary.
23              
24             If there's a failure, it throws a warning and returns an empty list.
25              
26             =cut
27              
28             sub new {
29 0     0 1   my $class = shift;
30 0           my $filename = shift;
31              
32 0           my $self = bless {
33             filename => $filename,
34             fh => undef,
35             }, $class;
36              
37 0 0         if ( $self->{filename} eq '-' ) {
38 0           $self->{fh} = *STDIN;
39             }
40              
41 0           return $self;
42             }
43              
44              
45             =head2 $file->name()
46              
47             Returns the name of the file.
48              
49             =cut
50              
51             sub name {
52 0     0 1   return $_[0]->{filename};
53             }
54              
55              
56             =head2 $file->basename()
57              
58             Returns the basename (the last component the path)
59             of the file.
60              
61             =cut
62              
63             sub basename {
64 0     0 1   my ( $self ) = @_;
65              
66 0   0       return $self->{basename} //= (File::Spec->splitpath($self->name))[2];
67             }
68              
69              
70             =head2 $file->open()
71              
72             Opens a filehandle for reading this file and returns it, or returns
73             undef if the operation fails (the error is in C<$!>). Instead of calling
74             C, C<$file-Eclose> should be called.
75              
76             =cut
77              
78             sub open {
79 0     0 1   my ( $self ) = @_;
80              
81 0 0         if ( !$self->{fh} ) {
82 0 0         if ( open $self->{fh}, '<', $self->{filename} ) {
83             # Do nothing.
84             }
85             else {
86 0           $self->{fh} = undef;
87             }
88             }
89              
90 0           return $self->{fh};
91             }
92              
93              
94             sub may_be_present {
95 0     0 0   my $self = shift;
96 0           my $regex = shift;
97              
98             # Tells if the file needs a line-by-line scan. This is a big
99             # optimization because if you can tell from the outset that the pattern
100             # is not found in the file at all, then there's no need to do the
101             # line-by-line iteration.
102              
103             # Slurp up an entire file up to 10M, see if there are any matches
104             # in it, and if so, let us know so we can iterate over it directly.
105              
106             # The $regex may be undef if it had a "$" in it, and is therefore unsuitable for this heuristic.
107              
108 0           my $may_be_present = 1;
109 0 0 0       if ( $regex && $self->open() && -f $self->{fh} ) {
      0        
110 0           my $buffer;
111 0           my $size = 10_000_000;
112 0           my $rc = sysread( $self->{fh}, $buffer, $size );
113 0 0         if ( !defined($rc) ) {
114 0 0         if ( $App::Ack::report_bad_filenames ) {
115 0           App::Ack::warn( $self->name . ": $!" );
116             }
117 0           $may_be_present = 0;
118             }
119             else {
120             # If we read all 10M, then we need to scan the rest.
121             # If there are any carriage returns, our results are flaky, so scan the rest.
122 0 0 0       if ( ($rc == $size) || (index($buffer,"\r") >= 0) ) {
123 0           $may_be_present = 1;
124             }
125             else {
126 0 0         if ( $buffer !~ /$regex/o ) {
127 0           $may_be_present = 0;
128             }
129             }
130             }
131             }
132              
133 0           return $may_be_present;
134             }
135              
136              
137             =head2 $file->reset()
138              
139             Resets the file back to the beginning. This is only called if
140             C is true, but not always if C
141             is true.
142              
143             =cut
144              
145             sub reset {
146 0     0 1   my $self = shift;
147              
148 0 0         if ( defined($self->{fh}) ) {
149 0 0         return unless -f $self->{fh};
150              
151 0 0 0       if ( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) {
152 0           App::Ack::warn( "$self->{filename}: $!" );
153             }
154             }
155              
156 0           return;
157             }
158              
159              
160             =head2 $file->close()
161              
162             Close the file.
163              
164             =cut
165              
166             sub close {
167 0     0 1   my $self = shift;
168              
169 0 0         if ( $self->{fh} ) {
170 0 0 0       if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) {
171 0           App::Ack::warn( $self->name() . ": $!" );
172             }
173 0           $self->{fh} = undef;
174             }
175              
176 0           return;
177             }
178              
179              
180             =head2 $file->clone()
181              
182             Clones this file.
183              
184             =cut
185              
186             sub clone {
187 0     0 1   my ( $self ) = @_;
188              
189 0           return __PACKAGE__->new($self->name);
190             }
191              
192              
193             =head2 $file->firstliney()
194              
195             Returns the first line of a file (or first 250 characters, whichever
196             comes first).
197              
198             =cut
199              
200             sub firstliney {
201 0     0 1   my ( $self ) = @_;
202              
203 0 0         if ( !exists $self->{firstliney} ) {
204 0           my $fh = $self->open();
205 0 0         if ( !$fh ) {
206 0 0         if ( $App::Ack::report_bad_filenames ) {
207 0           App::Ack::warn( $self->name . ': ' . $! );
208             }
209 0           $self->{firstliney} = '';
210             }
211             else {
212 0           my $buffer;
213 0           my $rc = sysread( $fh, $buffer, 250 );
214 0 0         if ( $rc ) {
215 0           $buffer =~ s/[\r\n].*//s;
216             }
217             else {
218 0 0         if ( !defined($rc) ) {
219 0           App::Ack::warn( $self->name . ': ' . $! );
220             }
221 0           $buffer = '';
222             }
223 0           $self->{firstliney} = $buffer;
224 0           $self->reset;
225             }
226             }
227              
228 0           return $self->{firstliney};
229             }
230              
231             1;