X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=gitmo%2FMouse.git;a=blobdiff_plain;f=lib%2FMouse%2FPurePerl.pm;h=f51b4c2540ff3ddfc4a56ae81767b51dc995e510;hp=23f7520b0dbfd62ac5760d0f137a3c58afcd58ca;hb=f7e41edaceee0d549a2d664378cfcf85dcf9454b;hpb=431657256f423bda264c0cb76c28de72fd879b20 diff --git a/lib/Mouse/PurePerl.pm b/lib/Mouse/PurePerl.pm index 23f7520..f51b4c2 100644 --- a/lib/Mouse/PurePerl.pm +++ b/lib/Mouse/PurePerl.pm @@ -1,3 +1,7 @@ +package Mouse::PurePerl; + +require Mouse::Util; + package Mouse::Util; @@ -66,23 +70,229 @@ sub get_code_package{ return $gv->STASH->NAME; } +sub get_code_ref{ + my($package, $name) = @_; + no strict 'refs'; + no warnings 'once'; + use warnings FATAL => 'uninitialized'; + return *{$package . '::' . $name}{CODE}; +} + +sub generate_isa_predicate_for { + my($for_class, $name) = @_; + + my $predicate = sub{ Scalar::Util::blessed($_[0]) && $_[0]->isa($for_class) }; + + 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); + +sub Any { 1 } +sub Item { 1 } + +sub Bool { $_[0] ? $_[0] eq '1' : 1 } +sub Undef { !defined($_[0]) } +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 Ref { ref($_[0]) } +sub ScalarRef { ref($_[0]) eq 'SCALAR' } +sub ArrayRef { ref($_[0]) eq 'ARRAY' } +sub HashRef { ref($_[0]) eq 'HASH' } +sub CodeRef { ref($_[0]) eq 'CODE' } +sub RegexpRef { ref($_[0]) eq 'Regexp' } +sub GlobRef { ref($_[0]) eq 'GLOB' } + +sub FileHandle { + openhandle($_[0]) || (blessed($_[0]) && $_[0]->isa("IO::Handle")) +} + +sub Object { blessed($_[0]) && blessed($_[0]) ne 'Regexp' } + +sub ClassName { Mouse::Util::is_class_loaded($_[0]) } +sub RoleName { (Mouse::Util::class_of($_[0]) || return 0)->isa('Mouse::Meta::Role') } + +sub _parameterize_ArrayRef_for { + my($type_parameter) = @_; + my $check = $type_parameter->_compiled_type_constraint; + + return sub { + foreach my $value (@{$_}) { + return undef unless $check->($value); + } + return 1; + } +} + +sub _parameterize_HashRef_for { + my($type_parameter) = @_; + my $check = $type_parameter->_compiled_type_constraint; + + return sub { + foreach my $value(values %{$_}){ + return undef unless $check->($value); + } + return 1; + }; +} + +# 'Maybe' type accepts 'Any', so it requires parameters +sub _parameterize_Maybe_for { + my($type_parameter) = @_; + my $check = $type_parameter->_compiled_type_constraint; + + return sub{ + return !defined($_) || $check->($_); + }; +}; + + + package Mouse::Meta::Module; -sub name { $_[0]->{package} } +sub name { $_[0]->{package} } + +sub _method_map { $_[0]->{methods} } +sub _attribute_map{ $_[0]->{attributes} } + +sub namespace{ + my $name = $_[0]->{package}; + no strict 'refs'; + return \%{ $name . '::' }; +} + +sub add_method { + my($self, $name, $code) = @_; + + if(!defined $name){ + $self->throw_error('You must pass a defined name'); + } + if(!defined $code){ + $self->throw_error('You must pass a defined code'); + } + + if(ref($code) ne 'CODE'){ + $code = \&{$code}; # coerce + } + + $self->{methods}->{$name} = $code; # Moose stores meta object here. + + my $pkg = $self->name; + no strict 'refs'; + no warnings 'redefine', 'once'; + *{ $pkg . '::' . $name } = $code; + return; +} + package Mouse::Meta::Class; +sub method_metaclass { $_[0]->{method_metaclass} || 'Mouse::Meta::Method' } +sub attribute_metaclass { $_[0]->{attribute_metaclass} || 'Mouse::Meta::Attribute' } + +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}; } sub roles { $_[0]->{roles} } +sub linearized_isa { @{ get_linear_isa($_[0]->{package}) } } + +sub get_all_attributes { + my($self) = @_; + my %attrs = map { %{ $self->initialize($_)->{attributes} } } reverse $self->linearized_isa; + return values %attrs; +} + +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; +} + + package Mouse::Meta::Role; +sub method_metaclass{ $_[0]->{method_metaclass} || 'Mouse::Meta::Role::Method' } + sub is_anon_role{ return exists $_[0]->{anon_serial_id}; } @@ -92,6 +302,9 @@ sub get_roles { $_[0]->{roles} } package Mouse::Meta::Attribute; +require Mouse::Meta::Method::Accessor; + +sub accessor_metaclass{ $_[0]->{accessor_metaclass} || 'Mouse::Meta::Method::Accessor' } # readers @@ -119,6 +332,8 @@ sub builder { $_[0]->{builder} } sub should_auto_deref { $_[0]->{auto_deref} } sub should_coerce { $_[0]->{coerce} } +sub documentation { $_[0]->{documentation} } + # predicates sub has_accessor { exists $_[0]->{accessor} } @@ -133,6 +348,8 @@ sub has_type_constraint { exists $_[0]->{type_constraint} } sub has_trigger { exists $_[0]->{trigger} } sub has_builder { exists $_[0]->{builder} } +sub has_documentation { exists $_[0]->{documentation} } + package Mouse::Meta::TypeConstraint; @@ -142,10 +359,143 @@ sub message { $_[0]->{message} } sub _compiled_type_constraint{ $_[0]->{compiled_type_constraint} } +sub _compiled_type_coercion { $_[0]->{_compiled_type_coercion} } + sub has_coercion{ exists $_[0]->{_compiled_type_coercion} } + +sub compile_type_constraint{ + my($self) = @_; + + # add parents first + my @checks; + for(my $parent = $self->{parent}; defined $parent; $parent = $parent->{parent}){ + if($parent->{hand_optimized_type_constraint}){ + unshift @checks, $parent->{hand_optimized_type_constraint}; + last; # a hand optimized constraint must include all the parents + } + elsif($parent->{constraint}){ + unshift @checks, $parent->{constraint}; + } + } + + # then add child + if($self->{constraint}){ + push @checks, $self->{constraint}; + } + + if($self->{type_constraints}){ # Union + my @types = map{ $_->{compiled_type_constraint} } @{ $self->{type_constraints} }; + push @checks, sub{ + foreach my $c(@types){ + return 1 if $c->($_[0]); + } + return 0; + }; + } + + if(@checks == 0){ + $self->{compiled_type_constraint} = \&Mouse::Util::TypeConstraints::Any; + } + else{ + $self->{compiled_type_constraint} = sub{ + my(@args) = @_; + local $_ = $args[0]; + foreach my $c(@checks){ + return undef if !$c->(@args); + } + return 1; + }; + } + return; +} + package - Mouse::Meta::Method::Accessor; + 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(); + } + }; + $@; + }; + + no warnings 'misc'; + die $e if $e; # rethrow +} 1; __END__ + +=head1 NAME + +Mouse::PurePerl - A Mouse guts in pure Perl + +=head1 VERSION + +This document describes Mouse version 0.40_08 + +=head1 SEE ALSO + +L + +=cut