1package TAP::Parser::Source; 2 3use strict; 4use vars qw($VERSION @ISA); 5 6use TAP::Object (); 7use File::Basename qw( fileparse ); 8 9use constant BLK_SIZE => 512; 10 11@ISA = qw(TAP::Object); 12 13=head1 NAME 14 15TAP::Parser::Source - a TAP source & meta data about it 16 17=head1 VERSION 18 19Version 3.26 20 21=cut 22 23$VERSION = '3.26'; 24 25=head1 SYNOPSIS 26 27 use TAP::Parser::Source; 28 my $source = TAP::Parser::Source->new; 29 $source->raw( \'reference to raw TAP source' ) 30 ->config( \%config ) 31 ->merge( $boolean ) 32 ->switches( \@switches ) 33 ->test_args( \@args ) 34 ->assemble_meta; 35 36 do { ... } if $source->meta->{is_file}; 37 # see assemble_meta for a full list of data available 38 39=head1 DESCRIPTION 40 41A TAP I<source> is something that produces a stream of TAP for the parser to 42consume, such as an executable file, a text file, an archive, an IO handle, a 43database, etc. C<TAP::Parser::Source>s encapsulate these I<raw> sources, and 44provide some useful meta data about them. They are used by 45L<TAP::Parser::SourceHandler>s, which do whatever is required to produce & 46capture a stream of TAP from the I<raw> source, and package it up in a 47L<TAP::Parser::Iterator> for the parser to consume. 48 49Unless you're writing a new L<TAP::Parser::SourceHandler>, a plugin or 50subclassing L<TAP::Parser>, you probably won't need to use this module directly. 51 52=head1 METHODS 53 54=head2 Class Methods 55 56=head3 C<new> 57 58 my $source = TAP::Parser::Source->new; 59 60Returns a new C<TAP::Parser::Source> object. 61 62=cut 63 64# new() implementation supplied by TAP::Object 65 66sub _initialize { 67 my ($self) = @_; 68 $self->meta( {} ); 69 $self->config( {} ); 70 return $self; 71} 72 73############################################################################## 74 75=head2 Instance Methods 76 77=head3 C<raw> 78 79 my $raw = $source->raw; 80 $source->raw( $some_value ); 81 82Chaining getter/setter for the raw TAP source. This is a reference, as it may 83contain large amounts of data (eg: raw TAP). 84 85=head3 C<meta> 86 87 my $meta = $source->meta; 88 $source->meta({ %some_value }); 89 90Chaining getter/setter for meta data about the source. This defaults to an 91empty hashref. See L</assemble_meta> for more info. 92 93=head3 C<has_meta> 94 95True if the source has meta data. 96 97=head3 C<config> 98 99 my $config = $source->config; 100 $source->config({ %some_value }); 101 102Chaining getter/setter for the source's configuration, if any has been provided 103by the user. How it's used is up to you. This defaults to an empty hashref. 104See L</config_for> for more info. 105 106=head3 C<merge> 107 108 my $merge = $source->merge; 109 $source->config( $bool ); 110 111Chaining getter/setter for the flag that dictates whether STDOUT and STDERR 112should be merged (where appropriate). Defaults to undef. 113 114=head3 C<switches> 115 116 my $switches = $source->switches; 117 $source->config([ @switches ]); 118 119Chaining getter/setter for the list of command-line switches that should be 120passed to the source (where appropriate). Defaults to undef. 121 122=head3 C<test_args> 123 124 my $test_args = $source->test_args; 125 $source->config([ @test_args ]); 126 127Chaining getter/setter for the list of command-line arguments that should be 128passed to the source (where appropriate). Defaults to undef. 129 130=cut 131 132sub raw { 133 my $self = shift; 134 return $self->{raw} unless @_; 135 $self->{raw} = shift; 136 return $self; 137} 138 139sub meta { 140 my $self = shift; 141 return $self->{meta} unless @_; 142 $self->{meta} = shift; 143 return $self; 144} 145 146sub has_meta { 147 return scalar %{ shift->meta } ? 1 : 0; 148} 149 150sub config { 151 my $self = shift; 152 return $self->{config} unless @_; 153 $self->{config} = shift; 154 return $self; 155} 156 157sub merge { 158 my $self = shift; 159 return $self->{merge} unless @_; 160 $self->{merge} = shift; 161 return $self; 162} 163 164sub switches { 165 my $self = shift; 166 return $self->{switches} unless @_; 167 $self->{switches} = shift; 168 return $self; 169} 170 171sub test_args { 172 my $self = shift; 173 return $self->{test_args} unless @_; 174 $self->{test_args} = shift; 175 return $self; 176} 177 178=head3 C<assemble_meta> 179 180 my $meta = $source->assemble_meta; 181 182Gathers meta data about the L</raw> source, stashes it in L</meta> and returns 183it as a hashref. This is done so that the L<TAP::Parser::SourceHandler>s don't 184have to repeat common checks. Currently this includes: 185 186 is_scalar => $bool, 187 is_hash => $bool, 188 is_array => $bool, 189 190 # for scalars: 191 length => $n 192 has_newlines => $bool 193 194 # only done if the scalar looks like a filename 195 is_file => $bool, 196 is_dir => $bool, 197 is_symlink => $bool, 198 file => { 199 # only done if the scalar looks like a filename 200 basename => $string, # including ext 201 dir => $string, 202 ext => $string, 203 lc_ext => $string, 204 # system checks 205 exists => $bool, 206 stat => [ ... ], # perldoc -f stat 207 empty => $bool, 208 size => $n, 209 text => $bool, 210 binary => $bool, 211 read => $bool, 212 write => $bool, 213 execute => $bool, 214 setuid => $bool, 215 setgid => $bool, 216 sticky => $bool, 217 is_file => $bool, 218 is_dir => $bool, 219 is_symlink => $bool, 220 # only done if the file's a symlink 221 lstat => [ ... ], # perldoc -f lstat 222 # only done if the file's a readable text file 223 shebang => $first_line, 224 } 225 226 # for arrays: 227 size => $n, 228 229=cut 230 231sub assemble_meta { 232 my ($self) = @_; 233 234 return $self->meta if $self->has_meta; 235 236 my $meta = $self->meta; 237 my $raw = $self->raw; 238 239 # rudimentary is object test - if it's blessed it'll 240 # inherit from UNIVERSAL 241 $meta->{is_object} = UNIVERSAL::isa( $raw, 'UNIVERSAL' ) ? 1 : 0; 242 243 if ( $meta->{is_object} ) { 244 $meta->{class} = ref($raw); 245 } 246 else { 247 my $ref = lc( ref($raw) ); 248 $meta->{"is_$ref"} = 1; 249 } 250 251 if ( $meta->{is_scalar} ) { 252 my $source = $$raw; 253 $meta->{length} = length($$raw); 254 $meta->{has_newlines} = $$raw =~ /\n/ ? 1 : 0; 255 256 # only do file checks if it looks like a filename 257 if ( !$meta->{has_newlines} and $meta->{length} < 1024 ) { 258 my $file = {}; 259 $file->{exists} = -e $source ? 1 : 0; 260 if ( $file->{exists} ) { 261 $meta->{file} = $file; 262 263 # avoid extra system calls (see `perldoc -f -X`) 264 $file->{stat} = [ stat(_) ]; 265 $file->{empty} = -z _ ? 1 : 0; 266 $file->{size} = -s _; 267 $file->{text} = -T _ ? 1 : 0; 268 $file->{binary} = -B _ ? 1 : 0; 269 $file->{read} = -r _ ? 1 : 0; 270 $file->{write} = -w _ ? 1 : 0; 271 $file->{execute} = -x _ ? 1 : 0; 272 $file->{setuid} = -u _ ? 1 : 0; 273 $file->{setgid} = -g _ ? 1 : 0; 274 $file->{sticky} = -k _ ? 1 : 0; 275 276 $meta->{is_file} = $file->{is_file} = -f _ ? 1 : 0; 277 $meta->{is_dir} = $file->{is_dir} = -d _ ? 1 : 0; 278 279 # symlink check requires another system call 280 $meta->{is_symlink} = $file->{is_symlink} 281 = -l $source ? 1 : 0; 282 if ( $file->{is_symlink} ) { 283 $file->{lstat} = [ lstat(_) ]; 284 } 285 286 # put together some common info about the file 287 ( $file->{basename}, $file->{dir}, $file->{ext} ) 288 = map { defined $_ ? $_ : '' } 289 fileparse( $source, qr/\.[^.]*/ ); 290 $file->{lc_ext} = lc( $file->{ext} ); 291 $file->{basename} .= $file->{ext} if $file->{ext}; 292 293 if ( !$file->{is_dir} && $file->{read} ) { 294 eval { $file->{shebang} = $self->shebang($$raw); }; 295 if ( my $e = $@ ) { 296 warn $e; 297 } 298 } 299 } 300 } 301 } 302 elsif ( $meta->{is_array} ) { 303 $meta->{size} = $#$raw + 1; 304 } 305 elsif ( $meta->{is_hash} ) { 306 ; # do nothing 307 } 308 309 return $meta; 310} 311 312=head3 C<shebang> 313 314Get the shebang line for a script file. 315 316 my $shebang = TAP::Parser::Source->shebang( $some_script ); 317 318May be called as a class method 319 320=cut 321 322{ 323 324 # Global shebang cache. 325 my %shebang_for; 326 327 sub _read_shebang { 328 my ( $class, $file ) = @_; 329 open my $fh, '<', $file or die "Can't read $file: $!\n"; 330 331 # Might be a binary file - so read a fixed number of bytes. 332 my $got = read $fh, my $buf, BLK_SIZE; 333 defined $got or die "I/O error: $!\n"; 334 return $1 if $buf =~ /(.*)/; 335 return; 336 } 337 338 sub shebang { 339 my ( $class, $file ) = @_; 340 $shebang_for{$file} = $class->_read_shebang($file) 341 unless exists $shebang_for{$file}; 342 return $shebang_for{$file}; 343 } 344} 345 346=head3 C<config_for> 347 348 my $config = $source->config_for( $class ); 349 350Returns L</config> for the $class given. Class names may be fully qualified 351or abbreviated, eg: 352 353 # these are equivalent 354 $source->config_for( 'Perl' ); 355 $source->config_for( 'TAP::Parser::SourceHandler::Perl' ); 356 357If a fully qualified $class is given, its abbreviated version is checked first. 358 359=cut 360 361sub config_for { 362 my ( $self, $class ) = @_; 363 my ($abbrv_class) = ( $class =~ /(?:\:\:)?(\w+)$/ ); 364 my $config = $self->config->{$abbrv_class} || $self->config->{$class}; 365 return $config; 366} 367 3681; 369 370__END__ 371 372=head1 AUTHORS 373 374Steve Purkis. 375 376=head1 SEE ALSO 377 378L<TAP::Object>, 379L<TAP::Parser>, 380L<TAP::Parser::IteratorFactory>, 381L<TAP::Parser::SourceHandler> 382 383=cut 384