1b39c5158Smillertpackage TAP::Parser::Source;
2b39c5158Smillert
3b39c5158Smillertuse strict;
46fb12b70Safresh1use warnings;
5b39c5158Smillert
6898184e3Ssthenuse File::Basename qw( fileparse );
76fb12b70Safresh1use base 'TAP::Object';
8b39c5158Smillert
991f110e0Safresh1use constant BLK_SIZE => 512;
1091f110e0Safresh1
11b39c5158Smillert=head1 NAME
12b39c5158Smillert
13898184e3SsthenTAP::Parser::Source - a TAP source & meta data about it
14b39c5158Smillert
15b39c5158Smillert=head1 VERSION
16b39c5158Smillert
17*3d61058aSafresh1Version 3.48
18b39c5158Smillert
19b39c5158Smillert=cut
20b39c5158Smillert
21*3d61058aSafresh1our $VERSION = '3.48';
22b39c5158Smillert
23b39c5158Smillert=head1 SYNOPSIS
24b39c5158Smillert
25b39c5158Smillert  use TAP::Parser::Source;
26b39c5158Smillert  my $source = TAP::Parser::Source->new;
27898184e3Ssthen  $source->raw( \'reference to raw TAP source' )
28898184e3Ssthen         ->config( \%config )
29898184e3Ssthen         ->merge( $boolean )
30898184e3Ssthen         ->switches( \@switches )
31898184e3Ssthen         ->test_args( \@args )
32898184e3Ssthen         ->assemble_meta;
33898184e3Ssthen
34898184e3Ssthen  do { ... } if $source->meta->{is_file};
35898184e3Ssthen  # see assemble_meta for a full list of data available
36b39c5158Smillert
37b39c5158Smillert=head1 DESCRIPTION
38b39c5158Smillert
39898184e3SsthenA TAP I<source> is something that produces a stream of TAP for the parser to
40898184e3Ssthenconsume, such as an executable file, a text file, an archive, an IO handle, a
41898184e3Ssthendatabase, etc.  C<TAP::Parser::Source>s encapsulate these I<raw> sources, and
42898184e3Ssthenprovide some useful meta data about them.  They are used by
43898184e3SsthenL<TAP::Parser::SourceHandler>s, which do whatever is required to produce &
44898184e3Ssthencapture a stream of TAP from the I<raw> source, and package it up in a
45898184e3SsthenL<TAP::Parser::Iterator> for the parser to consume.
46898184e3Ssthen
47898184e3SsthenUnless you're writing a new L<TAP::Parser::SourceHandler>, a plugin or
48898184e3Ssthensubclassing L<TAP::Parser>, you probably won't need to use this module directly.
49b39c5158Smillert
50b39c5158Smillert=head1 METHODS
51b39c5158Smillert
52b39c5158Smillert=head2 Class Methods
53b39c5158Smillert
54b39c5158Smillert=head3 C<new>
55b39c5158Smillert
56b39c5158Smillert my $source = TAP::Parser::Source->new;
57b39c5158Smillert
58b39c5158SmillertReturns a new C<TAP::Parser::Source> object.
59b39c5158Smillert
60b39c5158Smillert=cut
61b39c5158Smillert
62b39c5158Smillert# new() implementation supplied by TAP::Object
63b39c5158Smillert
64b39c5158Smillertsub _initialize {
65898184e3Ssthen    my ($self) = @_;
66898184e3Ssthen    $self->meta(   {} );
67898184e3Ssthen    $self->config( {} );
68b39c5158Smillert    return $self;
69b39c5158Smillert}
70b39c5158Smillert
71b39c5158Smillert##############################################################################
72b39c5158Smillert
73b39c5158Smillert=head2 Instance Methods
74b39c5158Smillert
75898184e3Ssthen=head3 C<raw>
76b39c5158Smillert
77898184e3Ssthen  my $raw = $source->raw;
78898184e3Ssthen  $source->raw( $some_value );
79b39c5158Smillert
80898184e3SsthenChaining getter/setter for the raw TAP source.  This is a reference, as it may
81898184e3Ssthencontain large amounts of data (eg: raw TAP).
82b39c5158Smillert
83898184e3Ssthen=head3 C<meta>
84b39c5158Smillert
85898184e3Ssthen  my $meta = $source->meta;
86898184e3Ssthen  $source->meta({ %some_value });
87b39c5158Smillert
88898184e3SsthenChaining getter/setter for meta data about the source.  This defaults to an
89898184e3Ssthenempty hashref.  See L</assemble_meta> for more info.
90b39c5158Smillert
91898184e3Ssthen=head3 C<has_meta>
92b39c5158Smillert
93898184e3SsthenTrue if the source has meta data.
94b39c5158Smillert
95898184e3Ssthen=head3 C<config>
96b39c5158Smillert
97898184e3Ssthen  my $config = $source->config;
98898184e3Ssthen  $source->config({ %some_value });
99b39c5158Smillert
100898184e3SsthenChaining getter/setter for the source's configuration, if any has been provided
101898184e3Ssthenby the user.  How it's used is up to you.  This defaults to an empty hashref.
102898184e3SsthenSee L</config_for> for more info.
103b39c5158Smillert
104b39c5158Smillert=head3 C<merge>
105b39c5158Smillert
106b39c5158Smillert  my $merge = $source->merge;
107898184e3Ssthen  $source->config( $bool );
108b39c5158Smillert
109898184e3SsthenChaining getter/setter for the flag that dictates whether STDOUT and STDERR
110898184e3Ssthenshould be merged (where appropriate).  Defaults to undef.
111898184e3Ssthen
112898184e3Ssthen=head3 C<switches>
113898184e3Ssthen
114898184e3Ssthen  my $switches = $source->switches;
115898184e3Ssthen  $source->config([ @switches ]);
116898184e3Ssthen
117898184e3SsthenChaining getter/setter for the list of command-line switches that should be
118898184e3Ssthenpassed to the source (where appropriate).  Defaults to undef.
119898184e3Ssthen
120898184e3Ssthen=head3 C<test_args>
121898184e3Ssthen
122898184e3Ssthen  my $test_args = $source->test_args;
123898184e3Ssthen  $source->config([ @test_args ]);
124898184e3Ssthen
125898184e3SsthenChaining getter/setter for the list of command-line arguments that should be
126898184e3Ssthenpassed to the source (where appropriate).  Defaults to undef.
127b39c5158Smillert
128b39c5158Smillert=cut
129b39c5158Smillert
130898184e3Ssthensub raw {
131898184e3Ssthen    my $self = shift;
132898184e3Ssthen    return $self->{raw} unless @_;
133898184e3Ssthen    $self->{raw} = shift;
134898184e3Ssthen    return $self;
135898184e3Ssthen}
136898184e3Ssthen
137898184e3Ssthensub meta {
138898184e3Ssthen    my $self = shift;
139898184e3Ssthen    return $self->{meta} unless @_;
140898184e3Ssthen    $self->{meta} = shift;
141898184e3Ssthen    return $self;
142898184e3Ssthen}
143898184e3Ssthen
144898184e3Ssthensub has_meta {
145898184e3Ssthen    return scalar %{ shift->meta } ? 1 : 0;
146898184e3Ssthen}
147898184e3Ssthen
148898184e3Ssthensub config {
149898184e3Ssthen    my $self = shift;
150898184e3Ssthen    return $self->{config} unless @_;
151898184e3Ssthen    $self->{config} = shift;
152898184e3Ssthen    return $self;
153898184e3Ssthen}
154898184e3Ssthen
155b39c5158Smillertsub merge {
156b39c5158Smillert    my $self = shift;
157b39c5158Smillert    return $self->{merge} unless @_;
158b39c5158Smillert    $self->{merge} = shift;
159b39c5158Smillert    return $self;
160b39c5158Smillert}
161b39c5158Smillert
162898184e3Ssthensub switches {
163898184e3Ssthen    my $self = shift;
164898184e3Ssthen    return $self->{switches} unless @_;
165898184e3Ssthen    $self->{switches} = shift;
166898184e3Ssthen    return $self;
167898184e3Ssthen}
168898184e3Ssthen
169898184e3Ssthensub test_args {
170898184e3Ssthen    my $self = shift;
171898184e3Ssthen    return $self->{test_args} unless @_;
172898184e3Ssthen    $self->{test_args} = shift;
173898184e3Ssthen    return $self;
174898184e3Ssthen}
175898184e3Ssthen
176898184e3Ssthen=head3 C<assemble_meta>
177898184e3Ssthen
178898184e3Ssthen  my $meta = $source->assemble_meta;
179898184e3Ssthen
180898184e3SsthenGathers meta data about the L</raw> source, stashes it in L</meta> and returns
181898184e3Ssthenit as a hashref.  This is done so that the L<TAP::Parser::SourceHandler>s don't
182898184e3Ssthenhave to repeat common checks.  Currently this includes:
183898184e3Ssthen
184898184e3Ssthen    is_scalar => $bool,
185898184e3Ssthen    is_hash   => $bool,
186898184e3Ssthen    is_array  => $bool,
187898184e3Ssthen
188898184e3Ssthen    # for scalars:
189898184e3Ssthen    length => $n
190898184e3Ssthen    has_newlines => $bool
191898184e3Ssthen
192898184e3Ssthen    # only done if the scalar looks like a filename
193898184e3Ssthen    is_file => $bool,
194898184e3Ssthen    is_dir  => $bool,
195898184e3Ssthen    is_symlink => $bool,
196898184e3Ssthen    file => {
197898184e3Ssthen        # only done if the scalar looks like a filename
198898184e3Ssthen        basename => $string, # including ext
199898184e3Ssthen        dir      => $string,
200898184e3Ssthen        ext      => $string,
201898184e3Ssthen        lc_ext   => $string,
202898184e3Ssthen        # system checks
203898184e3Ssthen        exists  => $bool,
204898184e3Ssthen        stat    => [ ... ], # perldoc -f stat
205898184e3Ssthen        empty   => $bool,
206898184e3Ssthen        size    => $n,
207898184e3Ssthen        text    => $bool,
208898184e3Ssthen        binary  => $bool,
209898184e3Ssthen        read    => $bool,
210898184e3Ssthen        write   => $bool,
211898184e3Ssthen        execute => $bool,
212898184e3Ssthen        setuid  => $bool,
213898184e3Ssthen        setgid  => $bool,
214898184e3Ssthen        sticky  => $bool,
215898184e3Ssthen        is_file => $bool,
216898184e3Ssthen        is_dir  => $bool,
217898184e3Ssthen        is_symlink => $bool,
218898184e3Ssthen        # only done if the file's a symlink
219898184e3Ssthen        lstat      => [ ... ], # perldoc -f lstat
220898184e3Ssthen        # only done if the file's a readable text file
221898184e3Ssthen        shebang => $first_line,
222898184e3Ssthen    }
223898184e3Ssthen
224898184e3Ssthen  # for arrays:
225898184e3Ssthen  size => $n,
226898184e3Ssthen
227898184e3Ssthen=cut
228898184e3Ssthen
229898184e3Ssthensub assemble_meta {
230898184e3Ssthen    my ($self) = @_;
231898184e3Ssthen
232898184e3Ssthen    return $self->meta if $self->has_meta;
233898184e3Ssthen
234898184e3Ssthen    my $meta = $self->meta;
235898184e3Ssthen    my $raw  = $self->raw;
236898184e3Ssthen
237898184e3Ssthen    # rudimentary is object test - if it's blessed it'll
238898184e3Ssthen    # inherit from UNIVERSAL
239898184e3Ssthen    $meta->{is_object} = UNIVERSAL::isa( $raw, 'UNIVERSAL' ) ? 1 : 0;
240898184e3Ssthen
241898184e3Ssthen    if ( $meta->{is_object} ) {
242898184e3Ssthen        $meta->{class} = ref($raw);
243898184e3Ssthen    }
244898184e3Ssthen    else {
245898184e3Ssthen        my $ref = lc( ref($raw) );
246898184e3Ssthen        $meta->{"is_$ref"} = 1;
247898184e3Ssthen    }
248898184e3Ssthen
249898184e3Ssthen    if ( $meta->{is_scalar} ) {
250898184e3Ssthen        my $source = $$raw;
251898184e3Ssthen        $meta->{length} = length($$raw);
252898184e3Ssthen        $meta->{has_newlines} = $$raw =~ /\n/ ? 1 : 0;
253898184e3Ssthen
254898184e3Ssthen        # only do file checks if it looks like a filename
255898184e3Ssthen        if ( !$meta->{has_newlines} and $meta->{length} < 1024 ) {
256898184e3Ssthen            my $file = {};
257898184e3Ssthen            $file->{exists} = -e $source ? 1 : 0;
258898184e3Ssthen            if ( $file->{exists} ) {
259898184e3Ssthen                $meta->{file} = $file;
260898184e3Ssthen
261898184e3Ssthen                # avoid extra system calls (see `perldoc -f -X`)
262898184e3Ssthen                $file->{stat}    = [ stat(_) ];
263898184e3Ssthen                $file->{empty}   = -z _ ? 1 : 0;
264898184e3Ssthen                $file->{size}    = -s _;
265898184e3Ssthen                $file->{text}    = -T _ ? 1 : 0;
266898184e3Ssthen                $file->{binary}  = -B _ ? 1 : 0;
267898184e3Ssthen                $file->{read}    = -r _ ? 1 : 0;
268898184e3Ssthen                $file->{write}   = -w _ ? 1 : 0;
269898184e3Ssthen                $file->{execute} = -x _ ? 1 : 0;
270898184e3Ssthen                $file->{setuid}  = -u _ ? 1 : 0;
271898184e3Ssthen                $file->{setgid}  = -g _ ? 1 : 0;
272898184e3Ssthen                $file->{sticky}  = -k _ ? 1 : 0;
273898184e3Ssthen
274898184e3Ssthen                $meta->{is_file} = $file->{is_file} = -f _ ? 1 : 0;
275898184e3Ssthen                $meta->{is_dir}  = $file->{is_dir}  = -d _ ? 1 : 0;
276898184e3Ssthen
277898184e3Ssthen                # symlink check requires another system call
278898184e3Ssthen                $meta->{is_symlink} = $file->{is_symlink}
279898184e3Ssthen                  = -l $source ? 1 : 0;
280898184e3Ssthen                if ( $file->{is_symlink} ) {
281898184e3Ssthen                    $file->{lstat} = [ lstat(_) ];
282898184e3Ssthen                }
283898184e3Ssthen
284898184e3Ssthen                # put together some common info about the file
285898184e3Ssthen                ( $file->{basename}, $file->{dir}, $file->{ext} )
286898184e3Ssthen                  = map { defined $_ ? $_ : '' }
287898184e3Ssthen                  fileparse( $source, qr/\.[^.]*/ );
288898184e3Ssthen                $file->{lc_ext} = lc( $file->{ext} );
289898184e3Ssthen                $file->{basename} .= $file->{ext} if $file->{ext};
290898184e3Ssthen
29191f110e0Safresh1                if ( !$file->{is_dir} && $file->{read} ) {
29291f110e0Safresh1                    eval { $file->{shebang} = $self->shebang($$raw); };
293898184e3Ssthen                    if ( my $e = $@ ) {
294898184e3Ssthen                        warn $e;
295898184e3Ssthen                    }
296898184e3Ssthen                }
297898184e3Ssthen            }
298898184e3Ssthen        }
299898184e3Ssthen    }
300898184e3Ssthen    elsif ( $meta->{is_array} ) {
301898184e3Ssthen        $meta->{size} = $#$raw + 1;
302898184e3Ssthen    }
303898184e3Ssthen    elsif ( $meta->{is_hash} ) {
304898184e3Ssthen        ;    # do nothing
305898184e3Ssthen    }
306898184e3Ssthen
307898184e3Ssthen    return $meta;
308898184e3Ssthen}
309898184e3Ssthen
310898184e3Ssthen=head3 C<shebang>
311898184e3Ssthen
312898184e3SsthenGet the shebang line for a script file.
313898184e3Ssthen
314898184e3Ssthen  my $shebang = TAP::Parser::Source->shebang( $some_script );
315898184e3Ssthen
316898184e3SsthenMay be called as a class method
317898184e3Ssthen
318898184e3Ssthen=cut
319898184e3Ssthen
320898184e3Ssthen{
321898184e3Ssthen
322898184e3Ssthen    # Global shebang cache.
323898184e3Ssthen    my %shebang_for;
324898184e3Ssthen
325898184e3Ssthen    sub _read_shebang {
32691f110e0Safresh1        my ( $class, $file ) = @_;
32791f110e0Safresh1        open my $fh, '<', $file or die "Can't read $file: $!\n";
32891f110e0Safresh1
32991f110e0Safresh1        # Might be a binary file - so read a fixed number of bytes.
3306fb12b70Safresh1        my $got = read $fh, my ($buf), BLK_SIZE;
33191f110e0Safresh1        defined $got or die "I/O error: $!\n";
33291f110e0Safresh1        return $1 if $buf =~ /(.*)/;
33391f110e0Safresh1        return;
334898184e3Ssthen    }
335898184e3Ssthen
336898184e3Ssthen    sub shebang {
337898184e3Ssthen        my ( $class, $file ) = @_;
338898184e3Ssthen        $shebang_for{$file} = $class->_read_shebang($file)
339898184e3Ssthen          unless exists $shebang_for{$file};
340898184e3Ssthen        return $shebang_for{$file};
341898184e3Ssthen    }
342898184e3Ssthen}
343898184e3Ssthen
344898184e3Ssthen=head3 C<config_for>
345898184e3Ssthen
346898184e3Ssthen  my $config = $source->config_for( $class );
347898184e3Ssthen
348898184e3SsthenReturns L</config> for the $class given.  Class names may be fully qualified
349898184e3Ssthenor abbreviated, eg:
350898184e3Ssthen
351898184e3Ssthen  # these are equivalent
352898184e3Ssthen  $source->config_for( 'Perl' );
353898184e3Ssthen  $source->config_for( 'TAP::Parser::SourceHandler::Perl' );
354898184e3Ssthen
355898184e3SsthenIf a fully qualified $class is given, its abbreviated version is checked first.
356898184e3Ssthen
357898184e3Ssthen=cut
358898184e3Ssthen
359898184e3Ssthensub config_for {
360898184e3Ssthen    my ( $self, $class ) = @_;
361898184e3Ssthen    my ($abbrv_class) = ( $class =~ /(?:\:\:)?(\w+)$/ );
362898184e3Ssthen    my $config = $self->config->{$abbrv_class} || $self->config->{$class};
363898184e3Ssthen    return $config;
364b39c5158Smillert}
365b39c5158Smillert
366b39c5158Smillert1;
367b39c5158Smillert
368898184e3Ssthen__END__
369b39c5158Smillert
370898184e3Ssthen=head1 AUTHORS
371b39c5158Smillert
372898184e3SsthenSteve Purkis.
373b39c5158Smillert
374b39c5158Smillert=head1 SEE ALSO
375b39c5158Smillert
376b39c5158SmillertL<TAP::Object>,
377b39c5158SmillertL<TAP::Parser>,
378898184e3SsthenL<TAP::Parser::IteratorFactory>,
379898184e3SsthenL<TAP::Parser::SourceHandler>
380b39c5158Smillert
381b39c5158Smillert=cut
382