Devel-Deanonymize
view release on metacpan or search on metacpan
lib/Devel/Deanonymize.pm view on Meta::CPAN
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut
package Devel::Deanonymize;
use strict;
use warnings FATAL => 'all';
use base 'Exporter';
our @EXPORT = qw(alterContent);
our $VERSION = "0.2.0"; # Do not change manually, changed automatically on `make build` target
my $include_pattern;
sub import {
# capture input parameters
$include_pattern = $_[1] ? $_[1] : die("Devel::Deanonymize: An include Pattern must be specified \n");
}
sub alterContent {
my $input = shift;
my $subName = shift;
# define everything in a sub, so Devel::Cover will DTRT
# NB this introduces no extra linefeeds so D::C's line numbers
# in reports match the file on disk
# - In general, we match only if <white_space>*ENDMARKER<white_space>*<end_of_line>
# - We only allow `1` without a semicolon if found at the very end
$input =~ s/(.*?package\s+\S+)(.*)^[\s]*(__END__|1;|1\Z|__DATA__)[\s]*$(.*)\Z/$1sub $subName {$2} $subName();$3$4/sgm;
# unhide private methods to avoid "Variable will not stay shared"
# warnings that appear due to change of applicable scoping rules
# Note: not '\s*' in the start of string, to avoid matching and
# removing blank lines before the private sub definitions.
$input =~ s/(^[\t| ]*)my\s+(\S+\s*=\s*sub.*)$/$1our $2/gm;
return $input
}
sub hasEndmarker {
my $input = shift;
if ($input =~ /^[\s]*(__END__|1;|1\Z|__DATA__)[\s]*$(.*)\Z/gms) {
return 1;
}
return 0;
}
sub modify_files {
# Internal notes:
# Basically, this code replaces every file path in @INC with a reference to an anonymous sub which wraps each
# file in sub classWrapper { $orig_content } classWrapper(); However, this sub is **not** necessarily run at INIT or UNITCHECK stage!
# NB, this also explains why its is possible to have $include_pattern "defined" at UNITCHECK even if its run **before** import()
# Also make sure each file either ends with __DATA__, __END__, or 1;
unshift @INC, sub {
my (undef, $filename) = @_;
return () if ($filename !~ /$include_pattern/);
if (my $found = (grep {-e $_} map {"$_/$filename"} grep {!ref} @INC)[0]) {
print "Devel::Deanonymize: $found" . "\n" if $ENV{DEANONYMIZE_DEBUG};
local $/ = undef;
open my $fh, '<', $found or die("Can't read module file $found\n");
my $module_text = <$fh>;
close $fh;
if (not hasEndmarker($module_text)) {
warn("Devel::Deanonymize: Found no endmarker in file `$filename` - skipping\n");
return ();
}
$module_text = alterContent($module_text, "_anon");
# filehandle on the scalar
open $fh, '<', \$module_text;
if ($ENV{DEANONYMIZE_DEBUG}) {
open my $mod_fh, '>', $found . "_mod.pl";
print $mod_fh $module_text;
close $mod_fh;
}
# and put it into %INC too so that it looks like we loaded the code
# from the file directly
$INC{$filename} = $found;
return $fh;
}
else {
return ();
}
};
}
# We call modify_files twice since depending on how a module is loaded (use or required) it is present in @INC at different stages
# Also, "double-modification" is not possible because we only alter non references
INIT {
modify_files();
}
UNITCHECK {
modify_files();
}
1;
( run in 1.181 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )