whole bunch of stuff
Stevan Little [Mon, 6 Feb 2006 07:08:42 +0000 (07:08 +0000)]
18 files changed:
TODO
examples/AttributesWithHistory.pod
examples/ClassEncapsulatedAttributes.pod
examples/InsideOutClass.pod
examples/InstanceCountingClass.pod
examples/LazyClass.pod
examples/Perl6Attribute.pod
lib/Class/MOP.pm
lib/Class/MOP/Attribute.pm
lib/Class/MOP/Class.pm
t/006_new_and_clone_metaclasses.t
t/014_attribute_introspection.t [new file with mode: 0644]
t/020_attribute.t
t/101_InstanceCountingClass_test.t
t/102_InsideOutClass_test.t
t/103_Perl6Attribute_test.t
t/104_AttributesWithHistory_test.t
t/105_ClassEncapsulatedAttributes_test.t

diff --git a/TODO b/TODO
index 38440fc..81b803b 100644 (file)
--- a/TODO
+++ b/TODO
@@ -10,7 +10,7 @@ This will simplify some code, and really is not very expensive anyway
 
 - clean up bootstrapping to include the accessors, etc for attributes
 
-(PARTIALLY DONE) - could use some tests 
+(DONE)
 
 Having all this meta-info is useful actually, so why not add it, and 
 let the methods get overwritten if they need to be, its a small price
@@ -18,7 +18,7 @@ to pay for what we get from it.
 
 - clean up all ->initialize($_[0]) handling
 
-(PARTIALLY DONE) - needs tests
+(DONE)
   
 We should always be sure that $_[0] is a package name, and not 
 a blessed intstance.
@@ -41,7 +41,7 @@ class to implement, as is the construct_instance.
 
 - General Purpose &new_object and &clone_object method
 
-(PARTIALLY DONE) - needs tests
+(PARTIALLY DONE) - needs more tests
 
 I seem to be writing a new method each time, but since we dont
 have a Object class to always inherit from, this is needed.
index fe712c8..95c4688 100644 (file)
@@ -95,7 +95,7 @@ AttributesWithHistory - An example attribute metaclass which keeps a history of
   
   sub new  {
       my $class = shift;
-      bless $class->meta->construct_instance(@_) => $class;
+      $class->meta->new_object(@_);
   }
   
 =head1 DESCRIPTION
index 6087196..80bf6bb 100644 (file)
@@ -5,7 +5,7 @@ package # hide the package from PAUSE
 use strict;
 use warnings;
 
-our $VERSION = '0.02';
+our $VERSION = '0.03';
 
 use base 'Class::MOP::Class';
 
@@ -111,7 +111,7 @@ ClassEncapsulatedAttributes - A set of example metaclasses with class encapsulat
   
   sub new  {
       my $class = shift;
-      bless $class->meta->construct_instance(@_) => $class;
+      $class->meta->new_object(@_);
   }
   
   package Bar;
index d82cd03..653f917 100644 (file)
@@ -104,8 +104,8 @@ InsideOutClass - A set of example metaclasses which implement the Inside-Out tec
   
   sub new  {
       my $class = shift;
-      bless $class->meta->construct_instance(@_) => $class;
-  }  
+      $class->meta->new_object(@_);
+  } 
 
   # now you can just use the class as normal
 
index 92ae097..5730517 100644 (file)
@@ -40,7 +40,7 @@ InstanceCountingClass - An example metaclass which counts instances
   
   sub new  {
       my $class = shift;
-      bless $class->meta->construct_instance(@_) => $class;
+      $class->meta->new_object(@_);
   }
 
   # ... meanwhile, somewhere in the code
index e3c0b42..e4c7635 100644 (file)
@@ -96,9 +96,9 @@ LazyClass - An example metaclass with lazy initialization
       default => sub { BinaryTree->new() }    
   ));    
   
-  sub new {
+  sub new  {
       my $class = shift;
-      bless $class->meta->construct_instance(@_) => $class;
+      $class->meta->new_object(@_);
   }
   
   # ... later in code
index 95cf71b..930c6cd 100644 (file)
@@ -47,7 +47,7 @@ Perl6Attribute - An example attribute metaclass for Perl 6 style attributes
   
   sub new  {
       my $class = shift;
-      bless $class->meta->construct_instance(@_) => $class;
+      $class->meta->new_object(@_);
   }
 
 =head1 DESCRIPTION
index f40f794..7b01847 100644 (file)
@@ -147,12 +147,16 @@ Class::MOP::Attribute->meta->add_method('new' => sub {
         
     (defined $name && $name)
         || confess "You must provide a name for the attribute";
-    (!exists $options{reader} && !exists $options{writer})
-        || confess "You cannot declare an accessor and reader and/or writer functions"
-            if exists $options{accessor};
-    $options{init_arg} = $name if not exists $options{init_arg};
+    $options{init_arg} = $name 
+        if not exists $options{init_arg};
 
-    bless $class->meta->construct_instance(name => $name, %options) => blessed($class) || $class;
+    # return the new object
+    $class->meta->new_object(name => $name, %options);
+});
+
+Class::MOP::Attribute->meta->add_method('clone' => sub {
+    my $self = shift;
+    $self->meta->clone_object($self, @_);    
 });
 
 1;
index c780a1d..dbdc2dc 100644 (file)
@@ -7,7 +7,7 @@ use warnings;
 use Carp         'confess';
 use Scalar::Util 'blessed', 'reftype', 'weaken';
 
-our $VERSION = '0.02';
+our $VERSION = '0.03';
 
 sub meta { 
     require Class::MOP::Class;
@@ -30,11 +30,8 @@ sub new {
         
     (defined $name && $name)
         || confess "You must provide a name for the attribute";
-    (!exists $options{reader} && !exists $options{writer})
-        || confess "You cannot declare an accessor and reader and/or writer functions"
-            if exists $options{accessor};
-    
-    $options{init_arg} = $name if not exists $options{init_arg};
+    $options{init_arg} = $name 
+        if not exists $options{init_arg};
             
     bless {
         name      => $name,
@@ -51,6 +48,19 @@ sub new {
 }
 
 # NOTE:
+# this is a primative (and kludgy) clone operation 
+# for now, it will be repleace in the Class::MOP
+# bootstrap with a proper one, however we know 
+# that this one will work fine for now.
+sub clone {
+    my $self    = shift;
+    my %options = @_;
+    (blessed($self))
+        || confess "Can only clone an instance";
+    return bless { %{$self}, %options } => blessed($self);
+}
+
+# NOTE:
 # the next bunch of methods will get bootstrapped 
 # away in the Class::MOP bootstrapping section
 
@@ -254,6 +264,8 @@ An attribute must (at the very least), have a C<$name>. All other
 C<%options> are contained added as key-value pairs. Acceptable keys
 are as follows:
 
+=item B<clone (%options)>
+
 =over 4
 
 =item I<init_arg>
index 529b4f6..7863468 100644 (file)
@@ -6,6 +6,7 @@ use warnings;
 
 use Carp         'confess';
 use Scalar::Util 'blessed', 'reftype';
+use Hash::Util   'lock_keys';
 use Sub::Name    'subname';
 use B            'svref_2object';
 
@@ -150,6 +151,63 @@ sub clone_object {
     bless $class->clone_instance($instance, @_) => blessed($instance);
 }
 
+#{
+#    sub _deep_clone {       
+#        my ($object, $cache) = @_;
+#        return $object unless ref($object);
+#        # check for an active cache
+#        return _deep_clone_ref($object, ($cache = {}), 'HASH') if not defined $cache;      
+#        # if we have it in the cache them return the cached clone
+#        return $cache->{$object} if exists $cache->{$object};
+#        # now try it as an object, which will in
+#        # turn try it as ref if its not an object
+#        # and store it in case we run into a circular ref
+#        $cache->{$object} = _deep_clone_object($object, $cache);    
+#    }
+#
+#    sub _deep_clone_object {
+#        my ($object, $cache) = @_;
+#        # check to see if its an object, with a clone method    
+#        # or if we have an object, with no clone method, then
+#        # we will respect its encapsulation, and not muck with 
+#        # its internals. Basically, we assume it does not want
+#        # to be cloned    
+#        return $cache->{$object} = ($object->can('clone') ? $object->clone() : $object) 
+#            if blessed($object);
+#        return $cache->{$object} = _deep_clone_ref($object, $cache);     
+#    }
+#
+#    sub _deep_clone_ref { 
+#        my ($object, $cache, $ref_type) = @_;
+#        $ref_type ||= ref($object);
+#        my ($clone, $tied);
+#        if ($ref_type eq 'HASH') {
+#            $clone = {};
+#            tie %{$clone}, ref $tied if $tied = tied(%{$object});    
+#            %{$clone} = map { ref($_) ? _deep_clone($_, $cache) : $_ } %{$object};
+#        } 
+#        elsif ($ref_type eq 'ARRAY') {
+#            $clone = [];
+#            tie @{$clone}, ref $tied if $tied = tied(@{$object});
+#            @{$clone} = map { ref($_) ? _deep_clone($_, $cache) : $_ } @{$object};
+#        } 
+#        elsif ($ref_type eq 'REF' or $ref_type eq 'SCALAR') {
+#            my $var = "";
+#            $clone = \$var;
+#            tie ${$clone}, ref $tied if $tied = tied(${$object});
+#            ${$clone} = _deep_clone(${$object}, $cache);
+#        } 
+#        else {
+#            # shallow copy reference to code, glob, regex
+#            $clone = $object;
+#        }
+#        # store it in our cache
+#        $cache->{$object} = $clone;
+#        # and return the clone
+#        return $clone;    
+#    }    
+#}
+
 sub clone_instance {
     my ($class, $instance, %params) = @_;
     (blessed($instance))
@@ -159,7 +217,7 @@ sub clone_instance {
     # instead of this cheap hack. I will 
     # add that in later. 
     # (use the Class::Cloneable::Util code)
-    my $clone = { %{$instance} }; 
+    my $clone = { %{$instance} }; #_deep_clone($instance); 
     foreach my $attr ($class->compute_all_applicable_attributes()) {
         my $init_arg = $attr->init_arg();
         # try to fetch the init arg from the %params ...        
index 3646cf4..68916b0 100644 (file)
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use Test::More tests => 29;
+use Test::More tests => 32;
 use Test::Exception;
 
 BEGIN {
@@ -92,6 +92,11 @@ my $bar = $bar_meta->new_object();
 isa_ok($bar, 'Bar');
 isa_ok($bar, 'Foo');
 
+my $baz = $baz_meta->new_object();
+isa_ok($baz, 'Baz');
+isa_ok($baz, 'Bar');
+isa_ok($baz, 'Foo');
+
 my $cloned_foo = $foo_meta->clone_object($foo);
 isa_ok($cloned_foo, 'Foo');
 
diff --git a/t/014_attribute_introspection.t b/t/014_attribute_introspection.t
new file mode 100644 (file)
index 0000000..d0c9ba9
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 38;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Class::MOP');        
+}
+
+{
+    my $meta = Class::MOP::Attribute->meta();
+    isa_ok($meta, 'Class::MOP::Class');
+    
+    my @methods = qw(
+        meta 
+        new clone
+        name
+        has_accessor  accessor
+        has_writer    writer
+        has_reader    reader
+        has_predicate predicate
+        has_init_arg  init_arg
+        has_default   default
+        
+        associated_class
+        attach_to_class detach_from_class
+        
+        generate_accessor_method
+        generate_reader_method
+        generate_writer_method
+        generate_predicate_method
+        
+        process_accessors
+        install_accessors
+        remove_accessors
+        );
+        
+    is_deeply(
+        [ sort @methods ],
+        [ sort $meta->get_method_list ],
+        '... our method list matches');        
+    
+    foreach my $method_name (@methods) {
+        ok($meta->has_method($method_name), '... Class::MOP::Attribute->has_method(' . $method_name . ')');
+    }
+    
+    my @attributes = qw(
+        name accessor reader writer predicate
+        init_arg default associated_class
+        );
+
+    is_deeply(
+        [ sort @attributes ],
+        [ sort $meta->get_attribute_list ],
+        '... our attribute list matches');
+    
+    foreach my $attribute_name (@attributes) {
+        ok($meta->has_attribute($attribute_name), '... Class::MOP::Attribute->has_attribute(' . $attribute_name . ')');        
+    }
+    
+    # We could add some tests here to make sure that 
+    # the attribute have the appropriate 
+    # accessor/reader/writer/predicate combinations, 
+    # but that is getting a little excessive so I  
+    # wont worry about it for now. Maybe if I get 
+    # bored I will do it.
+}
\ No newline at end of file
index fd0be90..77e3589 100644 (file)
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use Test::More tests => 65;
+use Test::More tests => 62;
 use Test::Exception;
 
 BEGIN {
@@ -22,7 +22,13 @@ BEGIN {
     ok(!$attr->has_accessor, '... $attr does not have an accessor');
     ok(!$attr->has_reader, '... $attr does not have an reader');
     ok(!$attr->has_writer, '... $attr does not have an writer');
-    ok(!$attr->has_default, '... $attr does not have an default');                
+    ok(!$attr->has_default, '... $attr does not have an default');  
+    
+    my $attr_clone = $attr->clone();
+    isa_ok($attr_clone, 'Class::MOP::Attribute');
+    isnt($attr, $attr_clone, '... but they are different instances');
+    
+    is_deeply($attr, $attr_clone, '... but they are the same inside');
 }
 
 {
@@ -41,7 +47,13 @@ BEGIN {
     
     ok(!$attr->has_accessor, '... $attr does not have an accessor');
     ok(!$attr->has_reader, '... $attr does not have an reader');
-    ok(!$attr->has_writer, '... $attr does not have an writer');               
+    ok(!$attr->has_writer, '... $attr does not have an writer');   
+    
+    my $attr_clone = $attr->clone();
+    isa_ok($attr_clone, 'Class::MOP::Attribute');
+    isnt($attr, $attr_clone, '... but they are different instances');
+    
+    is_deeply($attr, $attr_clone, '... but they are the same inside');                
 }
 
 {
@@ -63,7 +75,13 @@ BEGIN {
     is($attr->accessor, 'foo', '... $attr->accessor == foo');
     
     ok(!$attr->has_reader, '... $attr does not have an reader');
-    ok(!$attr->has_writer, '... $attr does not have an writer');               
+    ok(!$attr->has_writer, '... $attr does not have an writer');   
+    
+    my $attr_clone = $attr->clone();
+    isa_ok($attr_clone, 'Class::MOP::Attribute');
+    isnt($attr, $attr_clone, '... but they are different instnaces');
+    
+    is_deeply($attr, $attr_clone, '... but they are the same inside');                
 }
 
 {
@@ -87,30 +105,45 @@ BEGIN {
     ok($attr->has_writer, '... $attr does have an writer');
     is($attr->writer, 'set_foo', '... $attr->writer == set_foo');    
 
-    ok(!$attr->has_accessor, '... $attr does not have an accessor');    
+    ok(!$attr->has_accessor, '... $attr does not have an accessor'); 
+    
+    my $attr_clone = $attr->clone();
+    isa_ok($attr_clone, 'Class::MOP::Attribute');
+    isnt($attr, $attr_clone, '... but they are different instnaces');
+    
+    is_deeply($attr, $attr_clone, '... but they are the same inside');       
 }
 
-dies_ok {
+# NOTE:
+# the next three tests once tested that 
+# the code would fail, but we lifted the 
+# restriction so you can have an accessor 
+# along with a reader/writer pair (I mean 
+# why not really). So now they test that 
+# it works, which is kinda silly, but it 
+# tests the API change, so I keep it.
+
+lives_ok {
     Class::MOP::Attribute->new('$foo', (
         accessor => 'foo',
         reader   => 'get_foo',
     ));
-} '... cannot create accessors with reader/writers';
+} '... can create accessors with reader/writers';
 
-dies_ok {
+lives_ok {
     Class::MOP::Attribute->new('$foo', (
         accessor => 'foo',
         writer   => 'set_foo',
     ));
-} '... cannot create accessors with reader/writers';
+} '... can create accessors with reader/writers';
 
-dies_ok {
+lives_ok {
     Class::MOP::Attribute->new('$foo', (
         accessor => 'foo',
         reader   => 'get_foo',        
         writer   => 'set_foo',
     ));
-} '... cannot create accessors with reader/writers';
+} '... can create accessors with reader/writers';
 
 dies_ok {
     Class::MOP::Attribute->new();
@@ -139,25 +172,3 @@ dies_ok {
 dies_ok {
     Class::MOP::Attribute->remove_accessors(bless {} => 'Fail');
 } '... bad remove_accessors argument';
-
-
-{
-    my $meta = Class::MOP::Attribute->meta();
-    isa_ok($meta, 'Class::MOP::Class');
-    
-    foreach my $method_name (qw(
-        meta 
-        new
-        has_accessor accessor
-        has_writer   writer
-        has_reader   reader
-        has_init_arg init_arg
-        has_default  default
-        install_accessors
-        remove_accessors
-        )) {
-        ok($meta->has_method($method_name), '... Class::MOP::Attribute->has_method(' . $method_name . ')');
-    }
-    
-    
-}
index 9941c7d..0cc7b7a 100644 (file)
@@ -27,7 +27,7 @@ a simple demonstration of how to make a metaclass.
     
     sub new  {
         my $class = shift;
-        bless $class->meta->construct_instance(@_) => $class;
+        $class->meta->new_object(@_);
     }
     
     package Bar;
index 5341b19..fcd877d 100644 (file)
@@ -31,7 +31,7 @@ BEGIN {
     
     sub new  {
         my $class = shift;
-        bless $class->meta->construct_instance(@_) => $class;
+        $class->meta->new_object(@_);
     }
 }
 
index 8ad155c..d9e77bf 100644 (file)
@@ -22,8 +22,8 @@ BEGIN {
     
     sub new  {
         my $class = shift;
-        bless $class->meta->construct_instance(@_) => $class;
-    }        
+        $class->meta->new_object(@_);
+    }      
 }
 
 my $foo = Foo->new();
index ada9d67..8542ef0 100644 (file)
@@ -29,8 +29,8 @@ BEGIN {
     
     sub new  {
         my $class = shift;
-        bless $class->meta->construct_instance(@_) => $class;
-    }        
+        $class->meta->new_object(@_);
+    }   
 }
 
 my $foo = Foo->new();
index 712d35a..57b9760 100644 (file)
@@ -30,7 +30,7 @@ BEGIN {
     
     sub new  {
         my $class = shift;
-        bless $class->meta->construct_instance(@_) => $class;
+        $class->meta->new_object(@_);
     }
     
     package Bar;