<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package TAP::Parser::Source;

use strict;
use warnings;

use File::Basename qw( fileparse );
use base 'TAP::Object';

use constant BLK_SIZE =&gt; 512;

=head1 NAME

TAP::Parser::Source - a TAP source &amp; meta data about it

=head1 VERSION

Version 3.44

=cut

our $VERSION = '3.44';

=head1 SYNOPSIS

  use TAP::Parser::Source;
  my $source = TAP::Parser::Source-&gt;new;
  $source-&gt;raw( \'reference to raw TAP source' )
         -&gt;config( \%config )
         -&gt;merge( $boolean )
         -&gt;switches( \@switches )
         -&gt;test_args( \@args )
         -&gt;assemble_meta;

  do { ... } if $source-&gt;meta-&gt;{is_file};
  # see assemble_meta for a full list of data available

=head1 DESCRIPTION

A TAP I&lt;source&gt; is something that produces a stream of TAP for the parser to
consume, such as an executable file, a text file, an archive, an IO handle, a
database, etc.  C&lt;TAP::Parser::Source&gt;s encapsulate these I&lt;raw&gt; sources, and
provide some useful meta data about them.  They are used by
L&lt;TAP::Parser::SourceHandler&gt;s, which do whatever is required to produce &amp;
capture a stream of TAP from the I&lt;raw&gt; source, and package it up in a
L&lt;TAP::Parser::Iterator&gt; for the parser to consume.

Unless you're writing a new L&lt;TAP::Parser::SourceHandler&gt;, a plugin or
subclassing L&lt;TAP::Parser&gt;, you probably won't need to use this module directly.

=head1 METHODS

=head2 Class Methods

=head3 C&lt;new&gt;

 my $source = TAP::Parser::Source-&gt;new;

Returns a new C&lt;TAP::Parser::Source&gt; object.

=cut

# new() implementation supplied by TAP::Object

sub _initialize {
    my ($self) = @_;
    $self-&gt;meta(   {} );
    $self-&gt;config( {} );
    return $self;
}

##############################################################################

=head2 Instance Methods

=head3 C&lt;raw&gt;

  my $raw = $source-&gt;raw;
  $source-&gt;raw( $some_value );

Chaining getter/setter for the raw TAP source.  This is a reference, as it may
contain large amounts of data (eg: raw TAP).

=head3 C&lt;meta&gt;

  my $meta = $source-&gt;meta;
  $source-&gt;meta({ %some_value });

Chaining getter/setter for meta data about the source.  This defaults to an
empty hashref.  See L&lt;/assemble_meta&gt; for more info.

=head3 C&lt;has_meta&gt;

True if the source has meta data.

=head3 C&lt;config&gt;

  my $config = $source-&gt;config;
  $source-&gt;config({ %some_value });

Chaining getter/setter for the source's configuration, if any has been provided
by the user.  How it's used is up to you.  This defaults to an empty hashref.
See L&lt;/config_for&gt; for more info.

=head3 C&lt;merge&gt;

  my $merge = $source-&gt;merge;
  $source-&gt;config( $bool );

Chaining getter/setter for the flag that dictates whether STDOUT and STDERR
should be merged (where appropriate).  Defaults to undef.

=head3 C&lt;switches&gt;

  my $switches = $source-&gt;switches;
  $source-&gt;config([ @switches ]);

Chaining getter/setter for the list of command-line switches that should be
passed to the source (where appropriate).  Defaults to undef.

=head3 C&lt;test_args&gt;

  my $test_args = $source-&gt;test_args;
  $source-&gt;config([ @test_args ]);

Chaining getter/setter for the list of command-line arguments that should be
passed to the source (where appropriate).  Defaults to undef.

=cut

sub raw {
    my $self = shift;
    return $self-&gt;{raw} unless @_;
    $self-&gt;{raw} = shift;
    return $self;
}

sub meta {
    my $self = shift;
    return $self-&gt;{meta} unless @_;
    $self-&gt;{meta} = shift;
    return $self;
}

sub has_meta {
    return scalar %{ shift-&gt;meta } ? 1 : 0;
}

sub config {
    my $self = shift;
    return $self-&gt;{config} unless @_;
    $self-&gt;{config} = shift;
    return $self;
}

sub merge {
    my $self = shift;
    return $self-&gt;{merge} unless @_;
    $self-&gt;{merge} = shift;
    return $self;
}

sub switches {
    my $self = shift;
    return $self-&gt;{switches} unless @_;
    $self-&gt;{switches} = shift;
    return $self;
}

sub test_args {
    my $self = shift;
    return $self-&gt;{test_args} unless @_;
    $self-&gt;{test_args} = shift;
    return $self;
}

=head3 C&lt;assemble_meta&gt;

  my $meta = $source-&gt;assemble_meta;

Gathers meta data about the L&lt;/raw&gt; source, stashes it in L&lt;/meta&gt; and returns
it as a hashref.  This is done so that the L&lt;TAP::Parser::SourceHandler&gt;s don't
have to repeat common checks.  Currently this includes:

    is_scalar =&gt; $bool,
    is_hash   =&gt; $bool,
    is_array  =&gt; $bool,

    # for scalars:
    length =&gt; $n
    has_newlines =&gt; $bool

    # only done if the scalar looks like a filename
    is_file =&gt; $bool,
    is_dir  =&gt; $bool,
    is_symlink =&gt; $bool,
    file =&gt; {
        # only done if the scalar looks like a filename
        basename =&gt; $string, # including ext
        dir      =&gt; $string,
        ext      =&gt; $string,
        lc_ext   =&gt; $string,
        # system checks
        exists  =&gt; $bool,
        stat    =&gt; [ ... ], # perldoc -f stat
        empty   =&gt; $bool,
        size    =&gt; $n,
        text    =&gt; $bool,
        binary  =&gt; $bool,
        read    =&gt; $bool,
        write   =&gt; $bool,
        execute =&gt; $bool,
        setuid  =&gt; $bool,
        setgid  =&gt; $bool,
        sticky  =&gt; $bool,
        is_file =&gt; $bool,
        is_dir  =&gt; $bool,
        is_symlink =&gt; $bool,
        # only done if the file's a symlink
        lstat      =&gt; [ ... ], # perldoc -f lstat
        # only done if the file's a readable text file
        shebang =&gt; $first_line,
    }

  # for arrays:
  size =&gt; $n,

=cut

sub assemble_meta {
    my ($self) = @_;

    return $self-&gt;meta if $self-&gt;has_meta;

    my $meta = $self-&gt;meta;
    my $raw  = $self-&gt;raw;

    # rudimentary is object test - if it's blessed it'll
    # inherit from UNIVERSAL
    $meta-&gt;{is_object} = UNIVERSAL::isa( $raw, 'UNIVERSAL' ) ? 1 : 0;

    if ( $meta-&gt;{is_object} ) {
        $meta-&gt;{class} = ref($raw);
    }
    else {
        my $ref = lc( ref($raw) );
        $meta-&gt;{"is_$ref"} = 1;
    }

    if ( $meta-&gt;{is_scalar} ) {
        my $source = $$raw;
        $meta-&gt;{length} = length($$raw);
        $meta-&gt;{has_newlines} = $$raw =~ /\n/ ? 1 : 0;

        # only do file checks if it looks like a filename
        if ( !$meta-&gt;{has_newlines} and $meta-&gt;{length} &lt; 1024 ) {
            my $file = {};
            $file-&gt;{exists} = -e $source ? 1 : 0;
            if ( $file-&gt;{exists} ) {
                $meta-&gt;{file} = $file;

                # avoid extra system calls (see `perldoc -f -X`)
                $file-&gt;{stat}    = [ stat(_) ];
                $file-&gt;{empty}   = -z _ ? 1 : 0;
                $file-&gt;{size}    = -s _;
                $file-&gt;{text}    = -T _ ? 1 : 0;
                $file-&gt;{binary}  = -B _ ? 1 : 0;
                $file-&gt;{read}    = -r _ ? 1 : 0;
                $file-&gt;{write}   = -w _ ? 1 : 0;
                $file-&gt;{execute} = -x _ ? 1 : 0;
                $file-&gt;{setuid}  = -u _ ? 1 : 0;
                $file-&gt;{setgid}  = -g _ ? 1 : 0;
                $file-&gt;{sticky}  = -k _ ? 1 : 0;

                $meta-&gt;{is_file} = $file-&gt;{is_file} = -f _ ? 1 : 0;
                $meta-&gt;{is_dir}  = $file-&gt;{is_dir}  = -d _ ? 1 : 0;

                # symlink check requires another system call
                $meta-&gt;{is_symlink} = $file-&gt;{is_symlink}
                  = -l $source ? 1 : 0;
                if ( $file-&gt;{is_symlink} ) {
                    $file-&gt;{lstat} = [ lstat(_) ];
                }

                # put together some common info about the file
                ( $file-&gt;{basename}, $file-&gt;{dir}, $file-&gt;{ext} )
                  = map { defined $_ ? $_ : '' }
                  fileparse( $source, qr/\.[^.]*/ );
                $file-&gt;{lc_ext} = lc( $file-&gt;{ext} );
                $file-&gt;{basename} .= $file-&gt;{ext} if $file-&gt;{ext};

                if ( !$file-&gt;{is_dir} &amp;&amp; $file-&gt;{read} ) {
                    eval { $file-&gt;{shebang} = $self-&gt;shebang($$raw); };
                    if ( my $e = $@ ) {
                        warn $e;
                    }
                }
            }
        }
    }
    elsif ( $meta-&gt;{is_array} ) {
        $meta-&gt;{size} = $#$raw + 1;
    }
    elsif ( $meta-&gt;{is_hash} ) {
        ;    # do nothing
    }

    return $meta;
}

=head3 C&lt;shebang&gt;

Get the shebang line for a script file.

  my $shebang = TAP::Parser::Source-&gt;shebang( $some_script );

May be called as a class method

=cut

{

    # Global shebang cache.
    my %shebang_for;

    sub _read_shebang {
        my ( $class, $file ) = @_;
        open my $fh, '&lt;', $file or die "Can't read $file: $!\n";

        # Might be a binary file - so read a fixed number of bytes.
        my $got = read $fh, my ($buf), BLK_SIZE;
        defined $got or die "I/O error: $!\n";
        return $1 if $buf =~ /(.*)/;
        return;
    }

    sub shebang {
        my ( $class, $file ) = @_;
        $shebang_for{$file} = $class-&gt;_read_shebang($file)
          unless exists $shebang_for{$file};
        return $shebang_for{$file};
    }
}

=head3 C&lt;config_for&gt;

  my $config = $source-&gt;config_for( $class );

Returns L&lt;/config&gt; for the $class given.  Class names may be fully qualified
or abbreviated, eg:

  # these are equivalent
  $source-&gt;config_for( 'Perl' );
  $source-&gt;config_for( 'TAP::Parser::SourceHandler::Perl' );

If a fully qualified $class is given, its abbreviated version is checked first.

=cut

sub config_for {
    my ( $self, $class ) = @_;
    my ($abbrv_class) = ( $class =~ /(?:\:\:)?(\w+)$/ );
    my $config = $self-&gt;config-&gt;{$abbrv_class} || $self-&gt;config-&gt;{$class};
    return $config;
}

1;

__END__

=head1 AUTHORS

Steve Purkis.

=head1 SEE ALSO

L&lt;TAP::Object&gt;,
L&lt;TAP::Parser&gt;,
L&lt;TAP::Parser::IteratorFactory&gt;,
L&lt;TAP::Parser::SourceHandler&gt;

=cut
</pre></body></html>