Ref-Util-Rewriter

 view release on metacpan or  search on metacpan

lib/Ref/Util/Rewriter.pm  view on Meta::CPAN

package Ref::Util::Rewriter;
# ABSTRACT: Rewrite your code to use Ref::Util

use strict;
use warnings;

use PPI;
use Safe::Isa;
use Exporter   qw< import >;
use List::Util qw< first  >;

our @EXPORT_OK = qw< rewrite_string rewrite_file >;

my %reftype_to_reffunc = (
    SCALAR => 'is_scalarref',
    ARRAY  => 'is_arrayref',
    HASH   => 'is_hashref',
    CODE   => 'is_coderef',
    Regexp => 'is_regexpref',
    GLOB   => 'is_globref',
    IO     => 'is_ioref',
    REF    => 'is_refref',
);

sub rewrite_string {
    my $string = shift;
    my $res    = rewrite_doc( PPI::Document->new(\$string) );
    return $res;
}

sub rewrite_file {
    my $file    = shift;
    my $content = rewrite_doc( PPI::Document->new($file) );

    open my $fh, '>', $file
        or die "Failed to open file $file: $!";
    print {$fh} $content;
    close $fh;

    return $content;
}

sub rewrite_doc {
    my $doc            = shift or die;
    my $all_statements = $doc->find('PPI::Statement');
    $all_statements    = [] unless defined $all_statements;

    my @cond_ops       = qw<or || and &&>;
    my @new_statements;

    ALL_STATEMENTS:
    foreach my $statement ( @{$all_statements} ) {
        # if there's an "if()" statement, it appears as a Compound statement
        # and then each internal statement appears again,
        # causing duplication in results
        $statement->$_isa('PPI::Statement::Compound')
            and next;

        _handle_eval($statement);

        # find the 'ref' functions
        my $ref_subs = $statement->find( sub {

lib/Ref/Util/Rewriter.pm  view on Meta::CPAN

                    $after  = '}';

                    if ( $sib->content =~ qr{^q(.).*(.)$} ) {
                        $before = 'q' . $1;
                        $after  = $2;
                    }

                    $content = $sib->literal;
                } elsif ( $sib->isa('PPI::Token::Quote::Interpolate') ) {
                    $before = 'qq{';
                    $after  = '}';

                    if ( $sib->string =~ qr{^q(.).*(.)$} ) {
                        $before = $1;
                        $after  = $2;
                    }

                    $content = $sib->string;
                }

                $content
                    or next;

                # FIXME: this is very ugly....
                # FIXME need to escape for literals but the idea is there
                $sib->{'content'}
                    = $before . rewrite_string($content) . $after;

                # Idea:
                # my $p = PPI::Token::Quote::Double->new( rewrite_string($content) );
                # $sib->insert_before( $p );
                # $sib->delete;
                last;
            }
        }

    }
}

sub _create_statement {
    my ( $func, $args, $rest ) = @_;
    my $args_str = join '', @{$args};
    $args_str =~ s/^\s+//;
    $args_str =~ s/\s+$//;
    $args_str =~ s/^\(+//;
    $args_str =~ s/\)+$//;
    defined $rest or $rest = '';
    return PPI::Document::Fragment->new(\"$func($args_str)$rest");
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Ref::Util::Rewriter - Rewrite your code to use Ref::Util

=head1 VERSION

version 0.100

=head1 SYNOPSIS

    use Ref::Util::Rewriter qw< rewrite_string >;
    my $new_string = rewrite_string(
        q! if ( ref($foo) eq 'HASH' ) { ... } !
    );

    # $new_string = q! if ( is_hashref($foo) ) { ... } !;

    use Ref::Util::Rewriter qw< rewrite_file >;
    rewrite_file("script.pl"); # file was now rewritten

=head1 DESCRIPTION

B<Warning:> You should take into account that the meaning of
L<Ref::Util>'s functions are subject to change with regards to
blessed objects. This might change the rewriter code in the future
to be smarter. This might also mean this won't necessarily achieve
what you're expecting.

Run it, check the diff, check your code, run your code, then
(maybe) - knowing the risk you take and absolutely no liability on
me, my family, nor my pets - merge it.

This module rewrites Perl code to use L<Ref::Util> instead of your
regular calls to C<ref>. It is much substantially faster and avoids
several mistakes that haunt beginning and advanced Perl developers.

Please review L<Ref::Util> to fully understand the possible implications
of using it in your case instead of the built-in C<ref> function.

The following constructs of code are supported:

=over 4

=item * Simple statement conditions

    ref($foo) eq 'CODE'; # -> is_coderef($foo)
    ref $foo  eq 'CODE'; # -> is_coderef($foo)
    ref $foo;            # -> is_ref($foo)

=item * Compound statement conditions

    if ( ref($foo) eq 'HASH' ) {...} # -> if( is_hashref($foo) ) {...}
    if ( ref $foo  eq 'HASH' ) {...} # -> if( is_hashref($foo) ) {...}
    if ( ref $foo )            {...} # -> if( is_ref($foo) )     {...}

=item * Postfix logical conditions

    ref($foo) eq 'ARRAY' and ... # -> is_arrayref($foo) and ...
    ref($foo) eq 'ARRAY' or  ... # -> is_arrayref($foo) or  ...
    ref($foo)            or  ... # -> is_ref($foo)      or  ...

=back

The following types of references comparisons are recognized:

=over 4

=item * C<SCALAR> = C<is_scalarref>

=item * C<ARRAY> = C<is_arrayref>

=item * C<HASH> = C<is_hashref>

=item * C<CODE> = C<is_coderef>

=item * C<Regexp> = C<is_coderef>

=item * C<GLOB> = C<is_globref>

=item * C<IO> = C<is_ioref>

=item * C<REF> = C<is_refref>

=back

=head1 SUBROUTINES

=head2 rewrite_string($perl_code_string)

Receive a string representing Perl code and return a new string in which
all C<ref> calls are replaced with the appropriate calls to L<Ref::Util>.

=head2 rewrite_file($filename)

Receive a filename as a string and rewrite the file in place (thus the
file is altered) in which all C<ref> calls are replaced with the
appropriate calls to L<Ref::Util>.

Careful, this function changes your file in place. It is advised to put
your file in some revision control so you could see what changes it has
done and commit them if you accept them.

This does B<not> add a new statement to use L<Ref::Util>, you will still
need to do that yourself.

=head2 rewrite_doc

The guts of the module which uses a direct L<PPI::Document> object and
works on that. It is used internally, but you may call it yourself if
you so wish.

=head1 AUTHOR

Sawyer X

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2019 by Sawyer X.

This is free software, licensed under:

  The MIT (X11) License

=cut



( run in 1.106 second using v1.01-cache-2.11-cpan-39bf76dae61 )