| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package App::ChangeShebang 0.10; | 
| 2 | 2 |  |  | 2 |  | 166340 | use v5.16; | 
|  | 2 |  |  |  |  | 19 |  | 
| 3 | 2 |  |  | 2 |  | 9 | use warnings; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 59 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 2 |  |  | 2 |  | 1668 | use Getopt::Long (); | 
|  | 2 |  |  |  |  | 24429 |  | 
|  | 2 |  |  |  |  | 60 |  | 
| 6 | 2 |  |  | 2 |  | 1137 | use Pod::Usage (); | 
|  | 2 |  |  |  |  | 103052 |  | 
|  | 2 |  |  |  |  | 52 |  | 
| 7 | 2 |  |  | 2 |  | 1916 | use ExtUtils::MakeMaker (); | 
|  | 2 |  |  |  |  | 200262 |  | 
|  | 2 |  |  |  |  | 59 |  | 
| 8 | 2 |  |  | 2 |  | 14 | use File::Basename (); | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 28 |  | 
| 9 | 2 |  |  | 2 |  | 760 | use File::Temp (); | 
|  | 2 |  |  |  |  | 19216 |  | 
|  | 2 |  |  |  |  | 1709 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 0 |  |  | 0 | 0 | 0 | sub prompt { ExtUtils::MakeMaker::prompt(@_) } | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | sub new { | 
| 14 | 4 |  |  | 4 | 0 | 18756 | my $class = shift; | 
| 15 | 4 |  |  |  |  | 49 | bless {@_}, $class; | 
| 16 |  |  |  |  |  |  | } | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | sub parse_options { | 
| 19 | 4 |  |  | 4 | 0 | 16 | my ($self, @argv) = @_; | 
| 20 | 4 |  |  |  |  | 31 | my $parser = Getopt::Long::Parser->new( | 
| 21 |  |  |  |  |  |  | config => [qw(no_auto_abbrev no_ignore_case)], | 
| 22 |  |  |  |  |  |  | ); | 
| 23 |  |  |  |  |  |  | $parser->getoptionsfromarray( | 
| 24 |  |  |  |  |  |  | \@argv, | 
| 25 | 0 |  |  | 0 |  | 0 | "version|v" => sub { printf "%s %s\n", __PACKAGE__, __PACKAGE__->VERSION; exit }, | 
|  | 0 |  |  |  |  | 0 |  | 
| 26 |  |  |  |  |  |  | "quiet|q"   => \$self->{quiet}, | 
| 27 |  |  |  |  |  |  | "force|f"   => \$self->{force}, | 
| 28 | 0 |  |  | 0 |  | 0 | "help|h"    => sub { Pod::Usage::pod2usage(0) }, | 
| 29 | 4 | 50 |  |  |  | 400 | ) or Pod::Usage::pod2usage(1); | 
| 30 |  |  |  |  |  |  |  | 
| 31 | 4 |  |  |  |  | 2301 | my @file = @argv; | 
| 32 | 4 | 50 |  |  |  | 10 | if (!@file) { | 
| 33 | 0 |  |  |  |  | 0 | warn "Missing file arguments.\n"; | 
| 34 | 0 |  |  |  |  | 0 | Pod::Usage::pod2usage(1); | 
| 35 |  |  |  |  |  |  | } | 
| 36 | 4 |  |  |  |  | 11 | $self->{file} = \@file; | 
| 37 | 4 |  |  |  |  | 30 | $self; | 
| 38 |  |  |  |  |  |  | } | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | sub run { | 
| 41 | 4 |  |  | 4 | 0 | 11 | my $self = shift; | 
| 42 | 4 |  |  |  |  | 6 | for my $file (@{ $self->{file} }) { | 
|  | 4 |  |  |  |  | 12 |  | 
| 43 | 12 | 50 | 33 |  |  | 288 | next unless -f $file && !-l $file; | 
| 44 | 12 | 100 |  |  |  | 52 | next unless $self->is_perl_shebang( $file ); | 
| 45 | 11 | 50 |  |  |  | 36 | unless ($self->{force}) { | 
| 46 | 0 |  |  |  |  | 0 | my $anser = prompt "change shebang line of $file? (y/N)", "N"; | 
| 47 | 0 | 0 |  |  |  | 0 | next if $anser !~ /^y(es)?$/i; | 
| 48 |  |  |  |  |  |  | } | 
| 49 | 11 |  |  |  |  | 37 | $self->change_shebang($file); | 
| 50 | 11 | 50 |  |  |  | 54 | warn "changed shebang line of $file\n" unless $self->{quiet}; | 
| 51 |  |  |  |  |  |  | } | 
| 52 |  |  |  |  |  |  | } | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | sub is_perl_shebang { | 
| 55 | 12 |  |  | 12 | 0 | 26 | my ($self, $file) = @_; | 
| 56 | 12 | 50 |  |  |  | 417 | open my $fh, "<:raw", $file or die "open $file: $!\n"; | 
| 57 | 12 | 50 |  |  |  | 217 | read $fh, my $first, 100 or die "read $file: $!\n"; | 
| 58 | 12 | 100 |  |  |  | 273 | return $first =~ /^#!([^\n]*)perl/ ? 1 : 0; | 
| 59 |  |  |  |  |  |  | } | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | my $remove = do { | 
| 62 |  |  |  |  |  |  | my $s = qr/[ \t]*/; | 
| 63 |  |  |  |  |  |  | my $w = qr/[^\n]*/; | 
| 64 |  |  |  |  |  |  | my $running_under_some_shell = qr/\n* | 
| 65 |  |  |  |  |  |  | $s eval $s ['"] exec $w \n | 
| 66 |  |  |  |  |  |  | $s if $s (?:0|\$running_under_some_shell) $w \n | 
| 67 |  |  |  |  |  |  | /xsm; | 
| 68 |  |  |  |  |  |  | my $shebang = qr/\n* | 
| 69 |  |  |  |  |  |  | \#! $w \n | 
| 70 |  |  |  |  |  |  | /xsm; | 
| 71 |  |  |  |  |  |  | qr/\A(?:$running_under_some_shell|$shebang)+/; | 
| 72 |  |  |  |  |  |  | }; | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | sub change_shebang { | 
| 75 | 11 |  |  | 11 | 0 | 33 | my ($self, $file) = @_; | 
| 76 | 11 |  |  |  |  | 17 | my $content = do { | 
| 77 | 11 | 50 |  |  |  | 341 | open my $fh, "<:raw", $file or die "open $file: $!\n"; | 
| 78 | 11 |  |  |  |  | 55 | local $/; <$fh>; | 
|  | 11 |  |  |  |  | 340 |  | 
| 79 |  |  |  |  |  |  | }; | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 11 |  |  |  |  | 139 | $content =~ s/$remove//; | 
| 82 |  |  |  |  |  |  |  | 
| 83 | 11 |  |  |  |  | 138 | my $mode = (stat $file)[2]; | 
| 84 |  |  |  |  |  |  |  | 
| 85 | 11 |  |  |  |  | 394 | my ($tmp_fh, $tmp_name) = File::Temp::tempfile UNLINK => 0, DIR => File::Basename::dirname($file); | 
| 86 | 11 |  |  |  |  | 3538 | chmod $mode, $tmp_name; | 
| 87 | 11 |  |  |  |  | 29 | print {$tmp_fh} <<'...'; | 
|  | 11 |  |  |  |  | 55 |  | 
| 88 |  |  |  |  |  |  | #!/bin/sh | 
| 89 |  |  |  |  |  |  | exec "$(dirname "$0")"/perl -x "$0" "$@" | 
| 90 |  |  |  |  |  |  | #!perl | 
| 91 |  |  |  |  |  |  | ... | 
| 92 | 11 |  |  |  |  | 22 | print {$tmp_fh} $content; | 
|  | 11 |  |  |  |  | 19 |  | 
| 93 | 11 |  |  |  |  | 344 | close $tmp_fh; | 
| 94 | 11 | 50 |  |  |  | 1097 | rename $tmp_name, $file or die "rename $tmp_name, $file: $!\n"; | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | 1; | 
| 99 |  |  |  |  |  |  | __END__ |