File Coverage

blib/lib/Launcher/Cascade/FileReader.pm
Criterion Covered Total %
statement 62 70 88.5
branch 18 28 64.2
condition 9 11 81.8
subroutine 11 11 100.0
pod 4 4 100.0
total 104 124 83.8


line stmt bran cond sub pod time code
1              
2             package Launcher::Cascade::FileReader;
3              
4             =head1 NAME
5              
6             Launcher::Cascade::FileReader - a class to read a file or the output of a command, locally or through ssh.
7              
8             =head1 SYNOPSIS
9              
10             use Launcher::Cascade::FileReader;
11              
12             my $f = new Launcher::Cascade::FileReader
13             -path => q{date '+%H:%M:%S' |},
14             -host => q{host.domain},
15             ;
16             my $fh = $f->open();
17             while ( <$fh> ) {
18             print; # what time is it over there?
19             }
20             close $fh;
21              
22             =head1 DESCRIPTION
23              
24             The purpose of this class is to provide a file handle that gives access to a
25             file or the output of a command. If a host() is provided, the file or the
26             command are fetched through ssh, otherwise, run locally. The class takes care
27             of escaping quote characters as appropriate.
28              
29             =cut
30              
31 5     5   125738 use strict;
  5         25  
  5         170  
32 5     5   29 use warnings;
  5         10  
  5         150  
33              
34 5     5   25 use base qw( Launcher::Cascade );
  5         8  
  5         1992  
35              
36 5     5   2959 use Launcher::Cascade::ListOfStrings::Context;
  5         17  
  5         4656  
37              
38             =head2 Attributes
39              
40             =over 4
41              
42             =item B
43              
44             The path to the file to open. If the last character is a vertical bar (or
45             "pipe", ASCII 0x7c), path() will be considered a command and run. The standard
46             ouput will be available in the filehandle returned by method open().
47              
48             =item B
49              
50             If set, the path() will be considered remote, i.e., the command actually run by
51             method open() will be a C. Either the content of the remote file
52             or the standard output of the remote command will be made available in the
53             filehandle returned by open().
54              
55             =item B
56              
57             The remote user to login as. When omitted, ssh(1) will login with the same user
58             as the local user.
59              
60             =item B
61              
62             The filehandle as returned by method open().
63              
64             =item B
65              
66             An array reference containing the line that matched the pattern given to method
67             search(), plus lines of context if either of context_after() or
68             context_before() are not null.
69              
70             =item B
71              
72             =item B
73              
74             Determines the number of lines to include in context() after, respectively
75             before, a pattern has been matched. The number of lines in context() should be
76             context_before() + 1 + context_after(), unless the end of the file was reached
77             too soon to provide enough context after the match.
78              
79             Both attributes default to 0, so the default context contains only one line,
80             the one that matched the pattern.
81              
82             =back
83              
84             =cut
85              
86             Launcher::Cascade::make_accessors qw( path host user filehandle );
87             Launcher::Cascade::make_accessors_with_defaults
88             context_before => 0,
89             context_after => 0,
90             ;
91              
92             sub _context_arguments {
93              
94 5     5   8 my $self = shift;
95              
96 5         9 my $header = '-' x 0;
97             #$header .= ' Excerpt from ' . $self->path();
98             #$header .= ' on host ' . $self->host() if $self->host();
99            
100 5         57 return (-string_before => $header);
101             }
102             sub context {
103              
104 5     5 1 12 my $self = shift;
105              
106 5   66     39 my $old = $self->{_context} ||= new Launcher::Cascade::ListOfStrings::Context
107             -list => [],
108             $self->_context_arguments(),
109             ;
110 5 100       28 if ( @_ ) {
111 3 50       15 if ( UNIVERSAL::isa($_[0], 'Launcher::Cascade::ListOfStrings::Context') ) {
112 0         0 $self->{_context} = $_[0];
113             }
114             else {
115 3         11 $self->{_context} = new Launcher::Cascade::ListOfStrings::Context
116             -list => $_[0],
117             $self->_context_arguments(),
118             ;
119             }
120             }
121 5         45 return $old;
122             }
123              
124             =head2 Methods
125              
126             =over 4
127              
128             =item B
129              
130             Opens the file or command specified by attribute path(), possible over ssh on
131             remote host(), and returns a filehandle make its content (for a file) or
132             standard output (for a command) available for reading.
133              
134             =cut
135              
136             sub open {
137              
138 6     6 1 14 my $self = shift;
139 6         34 my $filename = $self->_prepare_command();
140              
141 6 100       380 open my $fh, $filename or die "Cannot read $filename: $!";
142 5         31 $self->filehandle($fh);
143 5         22 return $fh;
144             }
145              
146             =item B
147              
148             Closes the filehandle.
149              
150             =cut
151              
152             sub close {
153              
154 4     4 1 9 my $self = shift;
155              
156 4 50       16 if ( ! CORE::close $self->filehandle(undef) ) {
157 0         0 my $path = $self->path();
158 0         0 my $cmd = $path;
159 0 0       0 $cmd = '' unless $cmd =~ s/\s*\|$//;
160              
161 0 0       0 my $what = $self->host() ? 'ssh to host ' . $self->host()
    0          
162             : $cmd ? "external command ($cmd)"
163             : "closing of file $path"
164             ;
165 0 0       0 if ( $! ) {
166 0         0 die "$what failed: $!";
167             }
168             else {
169 0         0 die "$what returned status $?";
170             }
171             }
172             }
173              
174             sub _remote_cat {
175              
176 2     2   3 my $self = shift;
177 2         3 my $path = shift;
178 2         6 return "cat $path";
179             }
180              
181             sub _prepare_command {
182              
183 12     12   27 my $self = shift;
184              
185 12         39 my $path = $self->path();
186              
187 12 100       52 if ( $self->host() ) {
188 4         12 $path =~ s/'/'\\''/g;
189 4 100       20 if ( $path !~ s/\s*\|$// ) {
190 2         5 $path = $self->_remote_cat($path);
191             }
192 4   100     11 my $user = $self->user() || '';
193 4         9 my $host = $self->host();
194 4 100       12 $user .= '@' if $user;
195 4         24 return "ssh $user$host '$path' |";
196             }
197             else {
198 8 100       67 $path = "< $path" unless $path =~ /\|$/;
199 8         86 return $path;
200             }
201             }
202              
203             =item B I, I, ...
204              
205             Search the filehandle for Is and returns the index of the one that
206             matched (starting with 0), or C<-1> if the end of file was reached before any
207             pattern could be matched.
208              
209             Is should be regular expressions, possibly pre-compiled with the
210             C operator.
211              
212             After a search(), the context() attribute contains an arrayref containing the
213             line that matched, plus a number of context lines before and after the match,
214             as defined by the context_after() and context_before() attributes.
215              
216             The filehandle is closed after the search().
217              
218             =cut
219              
220             sub search {
221              
222 4     4 1 14 my $self = shift;
223              
224 4         11 my @pattern = @_;
225              
226             # a buffer to contain the context before the match
227 4         6 my @fifo = ();
228 4         16 my $fifo_size = $self->context_before() + 1;
229              
230 4         9 my $result = -1;
231              
232             # Let's open the file if not yet done
233 4   66     25 my $fh = $self->filehandle() || $self->open();
234 3         91 LINE: while ( <$fh> ) {
235             # Store the line in the context buffer
236 53         125 push @fifo, $_;
237 53 100       108 shift @fifo if @fifo > $fifo_size;
238              
239             # try all patterns
240 53         119 for ( my $i = 0 ; $i < @pattern ; $i++ ) {
241 58 100       359 if ( /$pattern[$i]/ ) {
242 2         5 $result = $i;
243 2         7 last LINE;
244             }
245             }
246             }
247              
248             # Now fetch the context after the match
249 3         11 $fifo_size += $self->context_after();
250 3   100     32 while ( defined($_ = <$fh>) && @fifo < $fifo_size ) {
251 6         56 push @fifo, $_;
252             }
253              
254 3         14 $self->context(\@fifo);
255 3         28 $self->close();
256 3         20 return $result;
257             }
258              
259             =back
260              
261             =head1 BUGS AND CAVEATS
262              
263             =over 4
264              
265             =item *
266              
267             ssh(1) must be in one of directories listed in the C environment variable.
268              
269             =item *
270              
271             there is nothing provided for non-interactive logging. The DSA or RSA key pairs
272             should be properly generated and ssh configured to avoid interactive login.
273              
274             =back
275              
276             =head1 SEE ALSO
277              
278             L.
279              
280             =head1 AUTHOR
281              
282             Cédric Bouvier C<< >>
283              
284             =head1 COPYRIGHT & LICENSE
285              
286             Copyright (C) 2006 Cédric Bouvier, All Rights Reserved.
287              
288             This program is free software; you can redistribute it and/or modify it under
289             the same terms as Perl itself.
290              
291             =cut
292              
293             1; # end of Launcher::Cascade::FileReader