From: Stevan Little Date: Mon, 23 Jan 2006 22:38:06 +0000 (+0000) Subject: Class::MOP - adding in some basic stuff X-Git-Tag: 0_02~26 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=8b978dd5214f3b79791a9aa2db3be291af2fae6e;p=gitmo%2FClass-MOP.git Class::MOP - adding in some basic stuff --- diff --git a/lib/Class/MOP.pm b/lib/Class/MOP.pm index 86069bf..52a45b2 100644 --- a/lib/Class/MOP.pm +++ b/lib/Class/MOP.pm @@ -4,8 +4,16 @@ package Class::MOP; use strict; use warnings; +use Scalar::Util 'blessed'; + our $VERSION = '0.01'; +my %METAS; +sub UNIVERSAL::meta { + my $class = blessed($_[0]) || $_[0]; + $METAS{$class} ||= Class::MOP::Class->initialize($class) +} + 1; __END__ @@ -86,27 +94,19 @@ to be created any more than nessecary. =over 4 -=item B +=item B ?@superclasses, + methods => ?%methods, + attributes => ?%attributes)> This returns the basic Class object, bringing the specified C<$package_name> into existence and adding any of the -C<@superclasses>, C<%methods> and C<%attributes> to it. +C<$package_version>, C<@superclasses>, C<%methods> and C<%attributes> +to it. -=item B +=item B -This returns the basic Class object, after examining the given -C<$package_name> and attempting to discover it's components (the -methods, attributes and superclasses). - -B: This method makes every attempt to ignore subroutines -which have been exported by other packages into this one. - -=item B - -This creates the actual Class object given a C<$package_name>, -an array of C<@superclasses>, a hash of C<%methods> and a hash -of C<%attributes>. This method is used by both C and -C. +This initializes a Class object for a given a C<$package_name>. =back @@ -114,7 +114,7 @@ C. =over 4 -=item +=item B This will construct and instance using the C<$canidate> as storage (currently only HASH references are supported). This will collect all @@ -129,12 +129,12 @@ found in the attribute meta-object. =over 4 -=item C +=item B This is a read-only attribute which returns the package name that the Class is stored in. -=item C +=item B This is a read-only attribute which returns the C<$VERSION> of the package the Class is stored in. @@ -145,13 +145,13 @@ package the Class is stored in. =over 4 -=item C +=item B This is a read-write attribute which represents the superclass relationships of this Class. Basically, it can get and set the C<@ISA> for you. -=item C +=item B This computes the a list of the Class's ancestors in the same order in which method dispatch will be done. @@ -160,9 +160,12 @@ in which method dispatch will be done. =head3 Methods +B: These methods makes every attempt to ignore subroutines +which have been exported by other packages into this one. + =over 4 -=item C +=item B This will take a C<$method_name> and CODE reference to that C<$method> and install it into the Class. @@ -172,31 +175,31 @@ other than use B to make sure it is tagged with the correct name, and therefore show up correctly in stack traces and such. -=item C +=item B This just provides a simple way to check if the Class implements a specific C<$method_name>. It will I however, attempt to check if the class inherits the method. -=item C +=item B This will return a CODE reference of the specified C<$method_name>, or return undef if that method does not exist. -=item C +=item B This will attempt to remove a given C<$method_name> from the Class. It will return the CODE reference that it has removed, and will attempt to use B to clear the methods associated name. -=item C +=item B This will return a list of method names for all I defined methods. It does B provide a list of all applicable methods, including any inherited ones. If you want a list of all applicable methods, use the C method. -=item C +=item B This will return a list of all the methods names this Class will support, taking into account inheritance. The list will be a list of @@ -204,7 +207,7 @@ HASH references, each one containing the following information; method name, the name of the class in which the method lives and a CODE reference for the actual method. -=item C +=item B This will traverse the inheritence hierarchy and locate all methods with a given C<$method_name>. Similar to @@ -215,7 +218,7 @@ lives and a CODE reference for the actual method. =back -=head2 Attributes +=head3 Attributes It should be noted that since there is no one consistent way to define the attributes of a class in Perl 5. These methods can only work with @@ -224,26 +227,26 @@ their own. =over 4 -=item C +=item B This stores a C<$attribute_meta_object> in the Class object and associates it with the C<$attribute_name>. Unlike methods, attributes within the MOP are stored as meta-information only. They will be used -later to construct instances from (see C above). More -details about the attribute meta-objects can be found in the L section of this document. +later to construct instances from (see C above). +More details about the attribute meta-objects can be found in the +L section of this document. -=item C +=item B Checks to see if this Class has an attribute by the name of C<$attribute_name> and returns a boolean. -=item C +=item B Returns the attribute meta-object associated with C<$attribute_name>, if none is found, it will return undef. -=item C +=item B This will remove the attribute meta-object stored at C<$attribute_name>, then return the removed attribute meta-object. @@ -252,13 +255,13 @@ B Removing an attribute will only affect future instances of the class, it will not make any attempt to remove the attribute from any existing instances of the class. -=item C +=item B This returns a list of attribute names which are defined in the local class. If you want a list of all applicable attributes for a class, use the C method. -=item C +=item B This will traverse the inheritance heirachy and return a list of HASH references for all the applicable attributes for this class. The HASH @@ -266,8 +269,111 @@ references will contain the following information; the attribute name, the class which the attribute is associated with and the actual attribute meta-object +=item B + +This will communicate with all of the classes attributes to create +and install the appropriate accessors. (see L +below for more details). + +=back + +=head2 The Attribute Protocol + +This protocol is almost entirely an invention of this module. This is +because Perl 5 does not have consistent notion of what is an attribute +of a class. There are so many ways in which this is done, and very few +(if any) are discoverable by this module. + +So, all that said, this module attempts to inject some order into this +chaos, by introducing a more consistent approach. + +=head3 Creation + +=over 4 + +=item B + + Class::MOP::Attribute->new('$foo' => ( + accessor => 'foo', # dual purpose get/set accessor + init_arg => '-foo', # class->new will look for a -foo key + default => 'BAR IS BAZ!' # if no -foo key is provided, use this + )); + + Class::MOP::Attribute->new('$.bar' => ( + reader => 'bar', # getter + writer => 'set_bar', # setter + init_arg => '-bar', # class->new will look for a -bar key + # no default value means it is undef + )); + +=back + +=head3 Informational + +=over 4 + +=item B + +=item B + +=item B + +=item B + +=item B + +=item B + =back +=head3 Informational predicates + +=over 4 + +=item B + +Returns true if this attribute uses a get/set accessor, and false +otherwise + +=item B + +Returns true if this attribute has a reader, and false otherwise + +=item B + +Returns true if this attribute has a writer, and false otherwise + +=item B + +Returns true if this attribute has a class intialization argument, and +false otherwise + +=item B + +Returns true if this attribute has a default value, and false +otherwise. + +=back + +=head3 Attribute Accessor generation + +=over 4 + +=item B + +This allows the attribute to generate code for it's own accessor +methods. This is mostly part of an internal protocol between the class +and it's own attributes, see the C method above. + +=back + +=head2 The Method Protocol + +This protocol is very small, since methods in Perl 5 are just +subroutines within the particular package. Basically all we do is to +bless the subroutine and provide some very simple introspection +methods for it. + =head1 SEE ALSO =over 4 diff --git a/lib/Class/MOP/Attribute.pm b/lib/Class/MOP/Attribute.pm new file mode 100644 index 0000000..491e1c2 --- /dev/null +++ b/lib/Class/MOP/Attribute.pm @@ -0,0 +1,91 @@ + +package Class::MOP::Attribute; + +use strict; +use warnings; + +use Carp 'confess'; + +our $VERSION = '0.01'; + +sub new { + my $class = shift; + my $name = shift; + my %options = @_; + + (defined $name && $name ne '') + || confess "You must provide a name for the attribute"; + + bless { + name => $name, + accessor => $options{accessor}, + reader => $options{reader}, + writer => $options{writer}, + init_arg => $options{init_arg}, + default => $options{default} + } => $class; +} + +sub name { (shift)->{name} } + +sub has_accessor { (shift)->{accessor} ? 1 : 0 } +sub accessor { (shift)->{accessor} } + +sub has_reader { (shift)->{reader} ? 1 : 0 } +sub reader { (shift)->{reader} } + +sub has_writer { (shift)->{writer} ? 1 : 0 } +sub writer { (shift)->{writer} } + +sub has_init_arg { (shift)->{init_arg} ? 1 : 0 } +sub init_arg { (shift)->{init_arg} } + +sub has_default { (shift)->{default} ? 1 : 0 } +sub default { (shift)->{default} } + +sub generate_accessor { + my $self = shift; + # ... +} + +1; + +__END__ + +=pod + +=head1 NAME + +Class::MOP::Attribute - Attribute Meta Object + +=head1 SYNOPSIS + + Class::MOP::Attribute->new('$foo' => ( + accessor => 'foo', # dual purpose get/set accessor + init_arg => '-foo', # class->new will look for a -foo key + default => 'BAR IS BAZ!' # if no -foo key is provided, use this + )); + + Class::MOP::Attribute->new('$.bar' => ( + reader => 'bar', # getter + writer => 'set_bar', # setter + init_arg => '-bar', # class->new will look for a -bar key + # no default value means it is undef + )); + +=head1 DESCRIPTION + +=head1 AUTHOR + +Stevan Little Estevan@iinteractive.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut \ No newline at end of file diff --git a/lib/Class/MOP/Class.pm b/lib/Class/MOP/Class.pm new file mode 100644 index 0000000..1609a77 --- /dev/null +++ b/lib/Class/MOP/Class.pm @@ -0,0 +1,114 @@ + +package Class::MOP::Class; + +use strict; +use warnings; + +use Carp 'confess'; +use Scalar::Util 'blessed'; +use Sub::Name 'subname'; +use B 'svref_2object'; + +our $VERSION = '0.01'; + +# Creation + +sub initialize { + my ($class, $package_name) = @_; + (defined $package_name) + || confess "You must pass a package name"; + bless \$package_name => $class; +} + +sub create { + my ($class, $package_name, $package_version, %options) = @_; + (defined $package_name) + || confess "You must pass a package name"; + my $code = "package $package_name;"; + $code .= "\$$package_name\:\:VERSION = '$package_version';" + if defined $package_version; + eval $code; + confess "creation of $package_name failed : $@" if $@; + my $meta = $package_name->meta; + $meta->superclasses(@{$options{superclasses}}) + if exists $options{superclasses}; + # ... rest to come later ... + return $meta; +} + +# Informational + +sub name { ${$_[0]} } + +sub version { + my $self = shift; + no strict 'refs'; + ${$self->name . '::VERSION'}; +} + +# Inheritance + +sub superclasses { + my $self = shift; + no strict 'refs'; + if (@_) { + my @supers = @_; + @{$self->name . '::ISA'} = @supers; + } + @{$self->name . '::ISA'}; +} + +sub class_precedence_list { + my $self = shift; + ( + $self->name, + map { + $_->meta->class_precedence_list() + } $self->superclasses() + ); +} + +## Private Utility Methods + +# borrowed from Class::Trait 0.20 - Thanks Ovid :) +sub _find_subroutine_package { + my $sub = shift; + my $package = ''; + eval { + my $stash = svref_2object($sub)->STASH; + $package = $stash->NAME + if $stash && $stash->can('NAME'); + }; + confess "Could not determine calling package: $@" + if $@; + return $package; +} + +1; + +__END__ + +=pod + +=head1 NAME + +Class::MOP::Class - Class Meta Object + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=head1 AUTHOR + +Stevan Little Estevan@iinteractive.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut \ No newline at end of file diff --git a/lib/Class/MOP/Method.pm b/lib/Class/MOP/Method.pm new file mode 100644 index 0000000..a15bd0f --- /dev/null +++ b/lib/Class/MOP/Method.pm @@ -0,0 +1,36 @@ + +package Class::MOP::Method; + +use strict; +use warnings; + +our $VERSION = '0.01'; + +1; + +__END__ + +=pod + +=head1 NAME + +Class::MOP::Method - Method Meta Object + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=head1 AUTHOR + +Stevan Little Estevan@iinteractive.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut \ No newline at end of file diff --git a/t/000_load.t b/t/000_load.t new file mode 100644 index 0000000..2a37287 --- /dev/null +++ b/t/000_load.t @@ -0,0 +1,13 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More no_plan => 1; + +BEGIN { + use_ok('Class::MOP'); + use_ok('Class::MOP::Class'); + use_ok('Class::MOP::Attribute'); + use_ok('Class::MOP::Method'); +} \ No newline at end of file diff --git a/t/001_basic.t b/t/001_basic.t new file mode 100644 index 0000000..9ffcea2 --- /dev/null +++ b/t/001_basic.t @@ -0,0 +1,69 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More no_plan => 1; + +BEGIN { + use_ok('Class::MOP'); + use_ok('Class::MOP::Class'); +} + +{ + package Foo; + our $VERSION = '0.01'; + + package Bar; + our @ISA = ('Foo'); +} + +my $Foo = Foo->meta(); +isa_ok($Foo, 'Class::MOP::Class'); + +my $Bar = Bar->meta(); +isa_ok($Bar, 'Class::MOP::Class'); + +is($Foo->name, 'Foo', '... Foo->name == Foo'); +is($Bar->name, 'Bar', '... Bar->name == Bar'); + +is($Foo->version, '0.01', '... Foo->version == 0.01'); +is($Bar->version, undef, '... Bar->version == undef'); + +is_deeply([$Foo->superclasses], [], '... Foo has no superclasses'); +is_deeply([$Bar->superclasses], ['Foo'], '... Bar->superclasses == (Foo)'); + +$Foo->superclasses('UNIVERSAL'); + +is_deeply([$Foo->superclasses], ['UNIVERSAL'], '... Foo->superclasses == (UNIVERSAL) now'); + +is_deeply( + [ $Foo->class_precedence_list ], + [ 'Foo', 'UNIVERSAL' ], + '... Foo->class_precedence_list == (Foo, UNIVERSAL)'); + +is_deeply( + [ $Bar->class_precedence_list ], + [ 'Bar', 'Foo', 'UNIVERSAL' ], + '... Bar->class_precedence_list == (Bar, Foo, UNIVERSAL)'); + +# create a class using Class::MOP::Class ... + +my $Baz = Class::MOP::Class->create( + 'Baz' => '0.10' => ( + superclasses => [ 'Bar' ] + )); +isa_ok($Baz, 'Class::MOP::Class'); +is(Baz->meta, $Baz, '... our metaclasses are singletons'); + +is($Baz->name, 'Baz', '... Baz->name == Baz'); +is($Baz->version, '0.10', '... Baz->version == 0.10'); + +is_deeply([$Baz->superclasses], ['Bar'], '... Baz->superclasses == (Bar)'); + +is_deeply( + [ $Baz->class_precedence_list ], + [ 'Baz', 'Bar', 'Foo', 'UNIVERSAL' ], + '... Baz->class_precedence_list == (Baz, Bar, Foo, UNIVERSAL)'); + +