this, sort of, works
Matt S Trout [Sat, 5 Dec 2009 05:09:35 +0000 (05:09 +0000)]
lib/MooseX/Antlers/EvalTracker.pm
lib/MooseX/Antlers/RefFilter.pm
lib/MooseX/Antlers/StealImport.pm [new file with mode: 0644]
t/lib/One.pm
t/one.t

index 8466698..d56a5aa 100644 (file)
@@ -2,11 +2,14 @@ package MooseX::Antlers::EvalTracker;
 
 use Moose;
 use MooseX::Antlers::ErrorThrower;
+use aliased 'MooseX::Antlers::RefFilter';
 use Scalar::Util qw(weaken refaddr);
 use PadWalker qw(closed_over);
 use Sub::Identify qw(sub_fullname);
 use B qw(perlstring);
 use namespace::clean -except => 'meta';
+use String::TT qw(tt strip);
+use Data::Dumper::Concise;
 
 has '_original_eval_closure' => (is => 'ro', lazy_build => 1);
 has '_our_eval_closure' => (is => 'ro', lazy_build => 1);
@@ -71,6 +74,36 @@ sub _eval_closure_called_for {
   #warn Dumper($body);
 }
 
+sub serialized_construction {
+  my ($self, $externals) = @_;
+  my $code = strip q{
+    package Class::MOP::Method::Generated;
+    use strict;
+    use warnings;
+  };
+  foreach my $recorded (@{$self->recorded_coderefs}) {
+    my ($cr, $captures, $body) = @{$recorded};
+    my $name = sub_fullname($cr);
+    my $name_string = perlstring($name);
+    my $filter = RefFilter->new(
+      external_mappings => $externals,
+      root_name => '$__captures'
+    );
+    my $filtered_captures = Dumper($filter->visit($captures));
+    my $fixup_code = $filter->fixup_code;
+    my $use_captures = $self->_generate_capture_constructor($captures);
+    $code .= strip tt q{
+      {
+        my $__captures = [% filtered_captures %];
+        [% fixup_code %]
+        [% use_captures %]
+        *[% name %] = Sub::Name::subname [% name_string %] => [% body %];
+      }
+    };
+  }
+  return $code;
+}
+    
 sub _generate_coderef_constructor {
   my ($self, $entry) = @_;
   my ($cr, $captures, $body) = @{$entry};
index 654bbc0..acfe798 100644 (file)
@@ -121,7 +121,7 @@ sub _fixup_code_for_externals {
       # is an attempt to modify a readonly value and perl will burst into tears
       # $whatever = \"foo"
       # is ok so if the match succeeds switch it to that
-      if ($l =~ m/^\${(.*)}$/) { $l = $1; $r = "\\".$r; }
+      if ($l =~ m/^\${(.*)}$/) { $l = $1; $r = "\\(".$r.")"; }
       $l.' = '.$r.';';
     }
     sort keys %$ext
diff --git a/lib/MooseX/Antlers/StealImport.pm b/lib/MooseX/Antlers/StealImport.pm
new file mode 100644 (file)
index 0000000..b961452
--- /dev/null
@@ -0,0 +1,56 @@
+package MooseX::Antlers::StealImport;
+
+use strict;
+use warnings FATAL => 'all';
+
+my %saved_import;
+my %saved_inc;
+
+sub import {
+  my ($class, %steal_classes) = @_;
+  foreach my $to_steal (keys %steal_classes) {
+    (my $pm_file = $to_steal) =~ s/::/\//g;
+    if (exists $INC{"${pm_file}.pm"}) {
+      $saved_inc{$to_steal} = $INC{"${pm_file}.pm"}
+    }
+    $INC{"${pm_file}.pm"} = __FILE__;
+    my %steal_methods = %{$steal_classes{$to_steal}};
+    {
+      no strict 'refs';
+      no warnings 'redefine';
+      $saved_import{$to_steal} = $to_steal->can('import');
+      my $do = delete $steal_methods{-do};
+      *{"${to_steal}::import"} = sub {
+        my $targ = caller;
+        $do->(@_) if $do;
+        foreach my $meth (keys %steal_methods) {
+          *{"${targ}::${meth}"} = $steal_methods{$meth};
+        }
+      };
+    }
+  }
+}
+
+sub unimport {
+  my ($class, @unsteal_classes) = @_;
+  foreach my $unsteal (@unsteal_classes) {
+    if (exists $saved_inc{$unsteal}) {
+      (my $pm_file = $unsteal) =~ s/::/\//g;
+      $INC{"${pm_file}.pm"} = delete $saved_inc{$unsteal};
+    }
+    if (defined $saved_import{$unsteal}) {
+      {
+        no strict 'refs';
+        no warnings 'redefine';
+        *{"${unsteal}::import"} = delete $saved_import{$unsteal};
+      }
+    } else {
+      {
+        no strict 'refs';
+        delete ${"${unsteal}::"}{import};
+      }
+    }
+  }
+}
+
+1;
index 519e901..8ffaf7f 100644 (file)
@@ -8,4 +8,6 @@ sub get_called_foo { $called_foo }
 
 has foo => (is => 'rw', required => 1, trigger => sub { $called_foo++ });
 
+__PACKAGE__->meta->make_immutable;
+
 1;
diff --git a/t/one.t b/t/one.t
index eddb112..aef210f 100644 (file)
--- a/t/one.t
+++ b/t/one.t
@@ -6,39 +6,140 @@ use aliased 'MooseX::Antlers::RefFilter';
 use B qw(perlstring);
 use lib 't/lib';
 use Test::More;
+use Test::Exception;
+use Class::Unload;
+use String::TT qw(tt strip);
+use IO::All qw(io);
 
 my %attr_refs;
-my %attr_methods;
+my %attr_et;
+my $im_et;
 
 {
   require Moose;
-  my $orig = Moose->can('import');
+  my $orig_import = Moose->can('import');
   no warnings 'redefine';
   local *Moose::import = sub {
     my $targ = caller;
-    Moose->$orig({ into => $targ });
+    Moose->$orig_import({ into => $targ });
     my $has = $targ->can('has');
     {
       no strict 'refs';
       *{"${targ}::has"} = sub {
-        $attr_refs{$_[0]} = RefTracker->trace_refs(
-          '$attributes{'.perlstring($_[0]).'}'
-          => \@_
-        );
+        $attr_refs{$_[0]} = [
+          map RefTracker->trace_refs( $_ => \@_ ),
+            '(\@_)', '$has_args{'.perlstring($_[0]).'}'
+        ];
         my $et = EvalTracker->new->enable;
         $has->(@_);
-        $attr_methods{$_[0]} = $et->recorded_coderefs;
+        $attr_et{$_[0]} = $et->disable;
+        return;
       };
     }
   };
+  my $orig_immutable = Moose::Meta::Class->can('make_immutable');
+  local *Moose::Meta::Class::make_immutable = sub {
+    my $et = EvalTracker->new->enable;
+    $orig_immutable->(@_);
+    $im_et = $et->disable;
+    return;
+  };
   require One;
 }
 
-ok(One->can('foo'), 'foo accessor installed');
+sub foo_called {
+  &cmp_ok(One->get_called_foo, '==', @_); # cmp_ok has a $$$;$ proto
+}
+
+sub test_One {
+
+  ok(One->can('foo'), 'foo accessor installed');
+
+  dies_ok { One->new } 'foo is required';
+
+  foo_called(0 => 'trigger not called yet');
+
+  my $one = One->new(foo => 1);
+
+  foo_called(1 => 'trigger called once (constructor)');
+
+  cmp_ok($one->foo, '==', 1, 'read ok');
+
+  foo_called(1 => 'trigger not called for read');
+
+  $one->foo(2);
+
+  foo_called(2 => 'trigger called for setter');
+}
+
+my %orig_src = map +($_ => join '', Dump(One->can($_))), qw(new DESTROY one);
+
+my $orig_foo_meta = Dump(One->meta);
+
+test_One();
 
 use Data::Dump::Streamer;
 
-my $orig_foo_src = Dump(One->can('foo'));
+my $one_source_code = io($INC{'One.pm'})->all;
+
+#warn $attr_et{'foo'}->serialized_construction($attr_refs{'foo'});
+
+#my @has = (
+
+my $foo_build = $attr_et{'foo'}->serialized_construction($attr_refs{'foo'}[0]);
+
+my $im_build = $im_et->serialized_construction($attr_refs{'foo'}[1]);
+
+my $preamble = strip tt q{
+  my %replay_has;
+  my %has_args;
+  BEGIN {
+    %replay_has = (
+      foo => sub {
+        [% foo_build %]
+      }
+    );
+  }
+  sub MooseX::Antlers::ImmutableHackFor::Foo::make_immutable {
+[% im_build %]
+  }
+  use MooseX::Antlers::StealImport
+    Moose => {
+      -do => sub {
+        strict->import;
+        warnings->import;
+      },
+      has => sub {
+        $has_args{$_[0]} = \@_;
+        ($replay_has{$_[0]}||die "Can't find replay for $_[0]")
+          ->(@_);
+      },
+      meta => sub { 'MooseX::Antlers::ImmutableHackFor::Foo' }
+    };
+};
+
+my $postamble = strip q{
+  no MooseX::Antlers::StealImport qw(Moose);
+};
+
+my $compiled = join("\n", $preamble, $one_source_code, $postamble);
+
+#warn $compiled; done_testing; exit 0;
+
+Class::Unload->unload('One');
+Class::MOP::remove_metaclass_by_name('One');
+
+eval $compiled; die "Shit. failed.\n\n${compiled}\n\nError: $@" if $@;
+
+use Data::Dumper::Concise;
+
+my %compiled_src = map +($_ => join '', Dump(One->can($_))), qw(new DESTROY one);
+
+#foreach my $method (qw(new DESTROY one)) {
+#  is($compiled_src{$method}, $orig_src{$method}, "${method} restored ok");
+#}
+
+test_One;
 
 # write test_class method that checks method including call
 # Class::Unload One
@@ -46,9 +147,8 @@ my $orig_foo_src = Dump(One->can('foo'));
 # eval compiled source
 # run test_class after that as well as before unload
 
-use Data::Dumper::Concise;
-
 #warn Dumper \%attr_refs;
-#warn Dumper \%attr_methods;
+#warn Dumper \%attr_et;
+#warn Dumper $im_et;
 
 done_testing;