File Coverage

blib/lib/File/Navigate.pm
Criterion Covered Total %
statement 6 72 8.3
branch 0 26 0.0
condition n/a
subroutine 2 12 16.6
pod 8 9 88.8
total 16 119 13.4


line stmt bran cond sub pod time code
1             package File::Navigate;
2 1     1   6506 use strict;
  1         2  
  1         34  
3 1     1   6 use warnings;
  1         2  
  1         834  
4              
5             =head1 NAME
6              
7             File::Navigate - Navigate freely inside a text file
8              
9             =head1 DESCRIPTION
10              
11             The module is a glorified wrapper for tell() and seek().
12              
13             It aims to simplify the creation of logfile analysis tools by
14             providing a facility to jump around freely inside the contents
15             of large files without creating the need to slurp excessive
16             amounts of data.
17              
18             =head1 SYNOPSIS
19              
20             use File::Navigate;
21             my $nav = File::Navigate->new('/var/log/messages');
22              
23             # Read what's below the "cursor":
24             my $first = $nav->get;
25              
26             # Advance the cursor before reading:
27             my $second = $nav->getnext;
28             my $third = $nav->getnext;
29              
30             # Advance the cursor by hand:
31             $nav->next;
32             my $fourth = $nav->get;
33              
34             # Position the cursor onto an arbitrary line:
35             $nav->cursor(10);
36             my $tenth = $nav->get;
37              
38             # Reverse the cursor one line backward:
39             $nav->prev;
40             my $ninth = $nav->get;
41              
42             # Reverse the cursor before reading:
43             my $eigth = $nav->getprev;
44              
45             # Read an arbitrary line:
46             my $sixth = $nav->get(6);
47              
48             =cut
49              
50             our @ISA = qw(Exporter);
51             our @EXPORT_OK = qw();
52             our $VERSION = '1.0';
53              
54             =head1 CLASS METHODS
55              
56             =head2 I
57              
58             Open the file and create an index of the lines inside of it.
59              
60             my $mapper = File::Navigate->new($filename);
61              
62             =cut
63              
64             sub new($){
65 0     0 1   my $class = shift;
66 0           my $file;
67 0 0         unless ($file = shift){
68 0           die "No file specified\n";
69             }
70 0 0         unless (-e $file){
71 0           die "File not found: $file\n";
72             }
73 0 0         unless (-r $file){
74 0           die "File not readable: $file\n";
75             }
76 0           my $self = {};
77 0           $self->{'cursor'} = 1;
78 0           $self->{'lineindex'} = {};
79 0           $self->{'lineindex'}->{1} = 0;
80 0 0         open my $fh, "$file"
81             or die "Can't open $file: $!\n";
82 0           while (<$fh>){
83 0           my $thisline = $.;
84 0           my $nextline = $thisline + 1;
85 0           $self->{'lineindex'}->{$nextline} = tell $fh;
86             }
87 0           $self->{'length'} = scalar(keys %{$self->{'lineindex'}}) - 1 ;
  0            
88 0           $self->{'fh'} = $fh;
89 0           bless $self;
90             }
91              
92             =head1 OBJECT METHODS
93              
94             =head2 I
95              
96             Returns the number of lines in the file ("wc -l")
97              
98             my $lines = $nav->count;
99              
100             =cut
101              
102             sub length(){
103 0     0 0   my $self = shift;
104 0           return $self->{'length'};
105             }
106              
107             =head2 I
108              
109             Returns the current cursor position and/or sets the cursor.
110              
111             my $cursor = $nav->cursor(); # Query cursor position.
112             my $cursor = $nav->cursor(10); # Set cursor to line 10
113              
114             =cut
115              
116             sub cursor($){
117 0     0 1   my $self = shift;
118 0 0         if (my $goto = shift){
119 0           $self->{'cursor'} = $goto;
120             }
121 0           return $self->{'cursor'};
122             }
123              
124             =head2 I
125              
126             Gets the line at the cursor position or at the given position.
127              
128             my $line = $nav->get(); # Get line at cursor
129             my $line = $nav->get(10); # Get line 10
130              
131             =cut
132              
133             sub get($){
134 0     0 1   my $self = shift;
135 0           my $fh = $self->{'fh'};
136              
137 0           my $getline;
138 0 0         $getline = $self->{'cursor'} unless ($getline = shift);
139              
140 0 0         if ($getline < 1){
    0          
141 0           warn "WARNING: Seek before first line.";
142 0           return undef;
143             }elsif($getline > $self->{'length'}){
144 0           warn "WARNING: Seek beyond last line.";
145 0           return undef;
146             }
147 0           seek ($fh, $self->{'lineindex'}->{$getline}, 0);
148 0           my $gotline = <$fh>;
149 0           chomp $gotline;
150 0           return $gotline;
151             }
152              
153             =head2 I
154              
155             Advance the cursor position by one line. Returns the new cursor position.
156             Returns I if the cursor is already on the last line.
157              
158             my $newcursor = $nav->next();
159              
160             =cut
161              
162             sub next(){
163 0     0 1   my $self = shift;
164 0 0         if ($self->{'cursor'} == $self->{'length'}){
165 0           return undef;
166             }
167 0           $self->{'cursor'}++;
168 0           return $self->{'cursor'};
169             }
170              
171             =head2 I
172              
173             Reverse the cursor position by one line. Returns the new cursor position.
174             Returns I if the cursor is already on line 1.
175              
176             my $newcursor = $nav->prev();
177              
178             =cut
179              
180             sub prev(){
181 0     0 1   my $self = shift;
182 0 0         if ($self->{'cursor'} == 1){
183 0           return undef;
184             }
185 0           $self->{'cursor'}--;
186 0           return $self->{'cursor'};
187             }
188              
189             =head2 I
190              
191             Advance to the next line and return it.
192             Returns I if the cursor is already on the last line.
193              
194             my $newcursor = $nav->getnext();
195              
196             =cut
197              
198             sub getnext(){
199 0     0 1   my $self = shift;
200 0 0         $self->next or return undef;
201 0           return $self->get;
202             }
203              
204             =head2 I
205              
206             Reverse to the previous line and return it:
207             Returns I if the cursor is already on line 1.
208              
209             my $newcursor = $nav->getprev();
210              
211             =cut
212              
213             sub getprev(){
214 0     0 1   my $self = shift;
215 0 0         $self->prev or return undef;
216 0           return $self->get;
217             }
218              
219             =head2 I
220              
221             Find lines containing given regex. Returns array with line numbers.
222              
223             my @lines = @{$nav->find(qr/foo/)};
224              
225             =cut
226              
227             sub find($){
228 0     0 1   my $self = shift;
229 0           my $regex = shift;
230            
231 0           my @results;
232 0           for (my $lineno = 1; $lineno <= $self->{'length'}; $lineno++){
233 0           my $line = $self->get($lineno);
234 0 0         if ($line =~ $regex){
235 0           push @results, $lineno;
236             }
237             }
238 0           return \@results;
239             }
240              
241             sub DESTROY(){
242 0     0     my $self = shift;
243 0           close $self->{'fh'};
244             }
245              
246             =head1 EXAMPLE
247              
248             I, the opposite of I, in Perl using File::Navigate:
249              
250             #!/usr/bin/perl -w
251             use strict;
252             use File::Navigate;
253            
254             foreach my $file (reverse(@ARGV)){
255             my $nav = File::Navigate->new($file);
256             # Force cursor beyond last line
257             $nav->cursor($nav->length()+1);
258             print $nav->get()."\n" while $nav->prev();
259             }
260              
261             =head1 BUGS
262              
263             Seems to lack proper error handling.
264              
265             =head1 LIMITATIONS
266              
267             Works only on plain text files. Sockets, STDIO etc. are not supported.
268              
269             =head1 PREREQUISITES
270              
271             Tested on Perl 5.6.1.
272              
273             =head1 STATUS
274              
275             Mostly harmless.
276              
277             =head1 AUTHOR
278              
279             Martin Schmitt
280              
281             =cut
282              
283             1;