X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=gitmo%2FMouse.git;a=blobdiff_plain;f=lib%2FMouse%2FPurePerl.pm;h=94d57d888749699a97a3d49a398afe7fd874508a;hp=f5e3599f5a4cd33fa069de391455d45cfed2efad;hb=9b923ac8adb9442186c3f7cc09cd4d307d6458d7;hpb=c5f6ad05f942b8a1aab0c85e55338a6e498a2bf0 diff --git a/lib/Mouse/PurePerl.pm b/lib/Mouse/PurePerl.pm index f5e3599..94d57d8 100644 --- a/lib/Mouse/PurePerl.pm +++ b/lib/Mouse/PurePerl.pm @@ -2,8 +2,7 @@ package Mouse::PurePerl; require Mouse::Util; -package - Mouse::Util; +package Mouse::Util; use strict; use warnings; @@ -12,21 +11,39 @@ use warnings FATAL => 'redefine'; # to avoid to load Mouse::PurePerl use B (); + +# taken from Class/MOP.pm +sub is_valid_class_name { + my $class = shift; + + return 0 if ref($class); + return 0 unless defined($class); + + return 1 if $class =~ /\A \w+ (?: :: \w+ )* \z/xms; + + return 0; +} + sub is_class_loaded { my $class = shift; return 0 if ref($class) || !defined($class) || !length($class); # walk the symbol table tree to avoid autovififying - # \*{${main::}{"Foo::"}} == \*main::Foo:: + # \*{${main::}{"Foo::"}{"Bar::"}} == \*main::Foo::Bar:: my $pack = \%::; foreach my $part (split('::', $class)) { - my $entry = \$pack->{$part . '::'}; + $part .= '::'; + return 0 if !exists $pack->{$part}; + + my $entry = \$pack->{$part}; return 0 if ref($entry) ne 'GLOB'; - $pack = *{$entry}{HASH} or return 0; + $pack = *{$entry}{HASH}; } + return 0 if !%{$pack}; + # check for $VERSION or @ISA return 1 if exists $pack->{VERSION} && defined *{$pack->{VERSION}}{SCALAR} && defined ${ $pack->{VERSION} }; @@ -92,9 +109,34 @@ sub generate_isa_predicate_for { return $predicate; } +sub generate_can_predicate_for { + my($methods_ref, $name) = @_; -package - Mouse::Util::TypeConstraints; + my @methods = @{$methods_ref}; + + my $predicate = sub{ + my($instance) = @_; + if(Scalar::Util::blessed($instance)){ + foreach my $method(@methods){ + if(!$instance->can($method)){ + return 0; + } + } + return 1; + } + return 0; + }; + + if(defined $name){ + no strict 'refs'; + *{ caller() . '::' . $name } = $predicate; + return; + } + + return $predicate; +} + +package Mouse::Util::TypeConstraints; use Scalar::Util qw(blessed looks_like_number openhandle); @@ -107,10 +149,16 @@ sub Defined { defined($_[0]) } sub Value { defined($_[0]) && !ref($_[0]) } sub Num { !ref($_[0]) && looks_like_number($_[0]) } sub Int { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ } -sub Str { defined($_[0]) && !ref($_[0]) } +sub Str { + my($value) = @_; + return defined($value) && ref(\$value) eq 'SCALAR'; +} sub Ref { ref($_[0]) } -sub ScalarRef { ref($_[0]) eq 'SCALAR' } +sub ScalarRef { + my($value) = @_; + return ref($value) eq 'SCALAR' +} sub ArrayRef { ref($_[0]) eq 'ARRAY' } sub HashRef { ref($_[0]) eq 'HASH' } sub CodeRef { ref($_[0]) eq 'CODE' } @@ -118,7 +166,7 @@ sub RegexpRef { ref($_[0]) eq 'Regexp' } sub GlobRef { ref($_[0]) eq 'GLOB' } sub FileHandle { - openhandle($_[0]) || (blessed($_[0]) && $_[0]->isa("IO::Handle")) + return openhandle($_[0]) || (blessed($_[0]) && $_[0]->isa("IO::Handle")) } sub Object { blessed($_[0]) && blessed($_[0]) ne 'Regexp' } @@ -158,12 +206,9 @@ sub _parameterize_Maybe_for { return sub{ return !defined($_) || $check->($_); }; -}; - - +} -package - Mouse::Meta::Module; +package Mouse::Meta::Module; sub name { $_[0]->{package} } @@ -199,9 +244,13 @@ sub add_method { return; } +package Mouse::Meta::Class; + +sub method_metaclass { $_[0]->{method_metaclass} || 'Mouse::Meta::Method' } +sub attribute_metaclass { $_[0]->{attribute_metaclass} || 'Mouse::Meta::Attribute' } -package - Mouse::Meta::Class; +sub constructor_class { $_[0]->{constructor_class} || 'Mouse::Meta::Method::Constructor' } +sub destructor_class { $_[0]->{destructor_class} || 'Mouse::Meta::Method::Destructor' } sub is_anon_class{ return exists $_[0]->{anon_serial_id}; @@ -213,12 +262,81 @@ sub linearized_isa { @{ get_linear_isa($_[0]->{package}) } } sub get_all_attributes { my($self) = @_; - my %attrs = map { %{ $self->initialize($_)->_attribute_map } } reverse $self->linearized_isa; + my %attrs = map { %{ $self->initialize($_)->{attributes} } } reverse $self->linearized_isa; return values %attrs; } -package - Mouse::Meta::Role; +sub new_object { + my $self = shift; + my %args = (@_ == 1 ? %{$_[0]} : @_); + + my $object = bless {}, $self->name; + + $self->_initialize_object($object, \%args); + return $object; +} + +sub _initialize_object{ + my($self, $object, $args, $ignore_triggers) = @_; + + my @triggers_queue; + + foreach my $attribute ($self->get_all_attributes) { + my $init_arg = $attribute->init_arg; + my $slot = $attribute->name; + + if (defined($init_arg) && exists($args->{$init_arg})) { + $object->{$slot} = $attribute->_coerce_and_verify($args->{$init_arg}, $object); + + weaken($object->{$slot}) + if ref($object->{$slot}) && $attribute->is_weak_ref; + + if ($attribute->has_trigger) { + push @triggers_queue, [ $attribute->trigger, $object->{$slot} ]; + } + } + else { # no init arg + if ($attribute->has_default || $attribute->has_builder) { + if (!$attribute->is_lazy) { + my $default = $attribute->default; + my $builder = $attribute->builder; + my $value = $builder ? $object->$builder() + : ref($default) eq 'CODE' ? $object->$default() + : $default; + + $object->{$slot} = $attribute->_coerce_and_verify($value, $object); + + weaken($object->{$slot}) + if ref($object->{$slot}) && $attribute->is_weak_ref; + } + } + elsif($attribute->is_required) { + $self->throw_error("Attribute (".$attribute->name.") is required"); + } + } + } + + if(!$ignore_triggers){ + foreach my $trigger_and_value(@triggers_queue){ + my($trigger, $value) = @{$trigger_and_value}; + $trigger->($object, $value); + } + } + + if($self->is_anon_class){ + $object->{__METACLASS__} = $self; + } + + return; +} + +sub is_immutable { $_[0]->{is_immutable} } + +sub __strict_constructor{ $_[0]->{strict_constructor} } + +package Mouse::Meta::Role; + +sub method_metaclass{ $_[0]->{method_metaclass} || 'Mouse::Meta::Role::Method' } sub is_anon_role{ return exists $_[0]->{anon_serial_id}; @@ -226,10 +344,11 @@ sub is_anon_role{ sub get_roles { $_[0]->{roles} } -package - Mouse::Meta::Attribute; +package Mouse::Meta::Attribute; -use Mouse::Meta::Method::Accessor; +require Mouse::Meta::Method::Accessor; + +sub accessor_metaclass{ $_[0]->{accessor_metaclass} || 'Mouse::Meta::Method::Accessor' } # readers @@ -275,15 +394,143 @@ sub has_builder { exists $_[0]->{builder} } sub has_documentation { exists $_[0]->{documentation} } -sub accessor_metaclass(){ 'Mouse::Meta::Method::Accessor' } +sub _process_options{ + my($class, $name, $args) = @_; + + # taken from Class::MOP::Attribute::new + + defined($name) + or $class->throw_error('You must provide a name for the attribute'); + + if(!exists $args->{init_arg}){ + $args->{init_arg} = $name; + } + + # 'required' requires eigher 'init_arg', 'builder', or 'default' + my $can_be_required = defined( $args->{init_arg} ); -package - Mouse::Meta::TypeConstraint; + if(exists $args->{builder}){ + # XXX: + # Moose refuses a CODE ref builder, but Mouse doesn't for backward compatibility + # This feature will be changed in a future. (gfx) + $class->throw_error('builder must be a defined scalar value which is a method name') + #if ref $args->{builder} || !defined $args->{builder}; + if !defined $args->{builder}; + + $can_be_required++; + } + elsif(exists $args->{default}){ + if(ref $args->{default} && ref($args->{default}) ne 'CODE'){ + $class->throw_error("References are not allowed as default values, you must " + . "wrap the default of '$name' in a CODE reference (ex: sub { [] } and not [])"); + } + $can_be_required++; + } + + if( $args->{required} && !$can_be_required ) { + $class->throw_error("You cannot have a required attribute ($name) without a default, builder, or an init_arg"); + } + + # taken from Mouse::Meta::Attribute->new and ->_process_args + + if(exists $args->{is}){ + my $is = $args->{is}; + + if($is eq 'ro'){ + $args->{reader} ||= $name; + } + elsif($is eq 'rw'){ + if(exists $args->{writer}){ + $args->{reader} ||= $name; + } + else{ + $args->{accessor} ||= $name; + } + } + elsif($is eq 'bare'){ + # do nothing, but don't complain (later) about missing methods + } + else{ + $is = 'undef' if !defined $is; + $class->throw_error("I do not understand this option (is => $is) on attribute ($name)"); + } + } + + my $tc; + if(exists $args->{isa}){ + $tc = $args->{type_constraint} = Mouse::Util::TypeConstraints::find_or_create_isa_type_constraint($args->{isa}); + } + + if(exists $args->{does}){ + if(defined $tc){ # both isa and does supplied + my $does_ok = do{ + local $@; + eval{ "$tc"->does($args) }; + }; + if(!$does_ok){ + $class->throw_error("Cannot have both an isa option and a does option because '$tc' does not do '$args->{does}' on attribute ($name)"); + } + } + else { + $tc = $args->{type_constraint} = Mouse::Util::TypeConstraints::find_or_create_does_type_constraint($args->{does}); + } + } + + if($args->{coerce}){ + defined($tc) + || $class->throw_error("You cannot have coercion without specifying a type constraint on attribute ($name)"); + + $args->{weak_ref} + && $class->throw_error("You cannot have a weak reference to a coerced value on attribute ($name)"); + } + + if ($args->{lazy_build}) { + exists($args->{default}) + && $class->throw_error("You can not use lazy_build and default for the same attribute ($name)"); + + $args->{lazy} = 1; + $args->{builder} ||= "_build_${name}"; + if ($name =~ /^_/) { + $args->{clearer} ||= "_clear${name}"; + $args->{predicate} ||= "_has${name}"; + } + else { + $args->{clearer} ||= "clear_${name}"; + $args->{predicate} ||= "has_${name}"; + } + } + + if ($args->{auto_deref}) { + defined($tc) + || $class->throw_error("You cannot auto-dereference without specifying a type constraint on attribute ($name)"); + + ( $tc->is_a_type_of('ArrayRef') || $tc->is_a_type_of('HashRef') ) + || $class->throw_error("You cannot auto-dereference anything other than a ArrayRef or HashRef on attribute ($name)"); + } + + if (exists $args->{trigger}) { + ('CODE' eq ref $args->{trigger}) + || $class->throw_error("Trigger must be a CODE ref on attribute ($name)"); + } + + if ($args->{lazy}) { + (exists $args->{default} || defined $args->{builder}) + || $class->throw_error("You cannot have lazy attribute ($name) without specifying a default value for it"); + } + + return; +} + + +package Mouse::Meta::TypeConstraint; sub name { $_[0]->{name} } sub parent { $_[0]->{parent} } sub message { $_[0]->{message} } +sub type_parameter { $_[0]->{type_parameter} } +sub __is_parameterized { exists $_[0]->{type_parameter} } + sub _compiled_type_constraint{ $_[0]->{compiled_type_constraint} } sub _compiled_type_coercion { $_[0]->{_compiled_type_coercion} } @@ -337,7 +584,95 @@ sub compile_type_constraint{ return; } +package Mouse::Object; + + +sub BUILDARGS { + my $class = shift; + + if (scalar @_ == 1) { + (ref($_[0]) eq 'HASH') + || $class->meta->throw_error("Single parameters to new() must be a HASH ref"); + + return {%{$_[0]}}; + } + else { + return {@_}; + } +} + +sub new { + my $class = shift; + + $class->meta->throw_error('Cannot call new() on an instance') if ref $class; + + my $args = $class->BUILDARGS(@_); + + my $meta = Mouse::Meta::Class->initialize($class); + my $self = $meta->new_object($args); + + # BUILDALL + if( $self->can('BUILD') ) { + for my $class (reverse $meta->linearized_isa) { + my $build = Mouse::Util::get_code_ref($class, 'BUILD') + || next; + + $self->$build($args); + } + } + + return $self; +} + +sub DESTROY { + my $self = shift; + + return unless $self->can('DEMOLISH'); # short circuit + + local $?; + + my $e = do{ + local $@; + eval{ + + # DEMOLISHALL + + # We cannot count on being able to retrieve a previously made + # metaclass, _or_ being able to make a new one during global + # destruction. However, we should still be able to use mro at + # that time (at least tests suggest so ;) + + foreach my $class (@{ Mouse::Util::get_linear_isa(ref $self) }) { + my $demolish = Mouse::Util::get_code_ref($class, 'DEMOLISH') + || next; + + $self->$demolish($Mouse::Util::in_global_destruction); + } + }; + $@; + }; + + no warnings 'misc'; + die $e if $e; # rethrow +} + +sub BUILDALL { + my $self = shift; + + # short circuit + return unless $self->can('BUILD'); + + for my $class (reverse $self->meta->linearized_isa) { + my $build = Mouse::Util::get_code_ref($class, 'BUILD') + || next; + + $self->$build(@_); + } + return; +} +sub DEMOLISHALL; +*DEMOLISHALL = \&DESTROY; 1; __END__ @@ -348,7 +683,7 @@ Mouse::PurePerl - A Mouse guts in pure Perl =head1 VERSION -This document describes Mouse version 0.40_05 +This document describes Mouse version 0.50_03 =head1 SEE ALSO