From: Jesse Luehrs Date: Sat, 5 Sep 2009 07:36:02 +0000 (-0500) Subject: allow init_meta generation with Moose::Exporter X-Git-Tag: 0.89_02~33^2~6 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=95056a1ecd68b4e3b63c08dfda4d3d10a8ba0e0f;p=gitmo%2FMoose.git allow init_meta generation with Moose::Exporter --- diff --git a/lib/Moose/Cookbook/Extending/Recipe1.pod b/lib/Moose/Cookbook/Extending/Recipe1.pod index 86e3307..c81b1a8 100644 --- a/lib/Moose/Cookbook/Extending/Recipe1.pod +++ b/lib/Moose/Cookbook/Extending/Recipe1.pod @@ -211,42 +211,35 @@ L to apply all of your roles. The advantage of using this module is that I. This means that your extension is cooperative I, and consumers of your -extension can easily use it with other role-based extensions. +extension can easily use it with other role-based extensions. Most +uses of L can be handled by L +directly; see the L docs. package MooseX::Embiggen; use Moose (); use Moose::Exporter; - use Moose::Util::MetaRole; use MooseX::Embiggen::Role::Meta::Class; use MooseX::Embiggen::Role::Meta::Attribute; use MooseX::Embiggen::Role::Meta::Method::Constructor; use MooseX::Embiggen::Role::Object; - Moose::Exporter->setup_import_methods( also => 'Moose' ); + my ($import, $unimport, $init_meta) = Moose::Exporter->build_import_methods( + also => ['Moose'] + metaclass_roles => ['MooseX::Embiggen::Role::Meta::Class'], + attribute_metaclass_roles => ['MooseX::Embiggen::Role::Meta::Attribute'], + constructor_class_roles => + ['MooseX::Embiggen::Role::Meta::Method::Constructor'], + base_class_roles => ['MooseX::Embiggen::Role::Object'], + install => [qw(import unimport)], + ); sub init_meta { - shift; # just your package name + my $package = shift; my %options = @_; - Moose->init_meta(%options); - - my $meta = Moose::Util::MetaRole::apply_metaclass_roles( - for_class => $options{for_class}, - metaclass_roles => ['MooseX::Embiggen::Role::Meta::Class'], - attribute_metaclass_roles => - ['MooseX::Embiggen::Role::Meta::Attribute'], - constructor_class_roles => - ['MooseX::Embiggen::Role::Meta::Method::Constructor'], - ); - - Moose::Util::MetaRole::apply_base_class_roles( - for_class => $options{for_class}, - roles => ['MooseX::Embiggen::Role::Object'], - ); - - return $meta; + return $package->$init_meta(%options); } As you can see from this example, you can use L @@ -269,8 +262,6 @@ as well as those from other modules: also => 'Moose', ); - sub init_meta { ... } - sub embiggen { my $caller = shift; $caller->meta()->embiggen(@_); diff --git a/lib/Moose/Cookbook/Extending/Recipe2.pod b/lib/Moose/Cookbook/Extending/Recipe2.pod index 324524c..a5ae83c 100644 --- a/lib/Moose/Cookbook/Extending/Recipe2.pod +++ b/lib/Moose/Cookbook/Extending/Recipe2.pod @@ -9,25 +9,11 @@ Moose::Cookbook::Extending::Recipe2 - Providing a role for the base object class package MooseX::Debugging; - use Moose (); use Moose::Exporter; - use Moose::Util::MetaRole; - Moose::Exporter->setup_import_methods; - - sub init_meta { - shift; - my %options = @_; - - my $meta = Moose->init_meta(%options); - - Moose::Util::MetaRole::apply_base_class_roles( - for_class => $options{for_class}, - roles => ['MooseX::Debugging::Role::Object'], - ); - - return $meta; - } + Moose::Exporter->setup_import_methods( + base_class_roles => ['MooseX::Debugging::Role::Object'], + ); package MooseX::Debugging::Role::Object; @@ -55,26 +41,17 @@ to its base object class. There are a few pieces of code worth looking at more closely. - Moose::Exporter->setup_import_methods; + Moose::Exporter->setup_import_methods( + base_class_roles => ['MooseX::Debugging::Role::Object'], + ); This creates an C method in the C package. Since we are not actually exporting anything, we do not pass -C any parameters. However, we need to have an -C method to ensure that our C method is called. - -Then in our C method we have this line: - - Moose->init_meta(%options); - -This is a bit of boilerplate that almost every extension will -use. This ensures that the caller has a normal Moose metaclass -I we go and add traits to it. - -The C<< Moose->init_meta >> method does ensures that the caller has a -sane metaclass, and we don't want to replicate that logic in our -extension. If the C<< Moose->init_meta >> was already called (because -the caller did C> before using our extension), then -calling C<< Moose->init_meta >> again is effectively a no-op. +C any parameters related to exports, but we need +to have an C method to ensure that our C method is +called. This call also automatically generates the appropriate +C, which will pass the C option through +to L. =head1 AUTHOR diff --git a/lib/Moose/Exporter.pm b/lib/Moose/Exporter.pm index 5598101..76e0fad 100644 --- a/lib/Moose/Exporter.pm +++ b/lib/Moose/Exporter.pm @@ -20,11 +20,10 @@ sub setup_import_methods { my $exporting_package = $args{exporting_package} ||= caller(); - my ( $import, $unimport ) = $class->build_import_methods(%args); - - no strict 'refs'; - *{ $exporting_package . '::import' } = $import; - *{ $exporting_package . '::unimport' } = $unimport; + $class->build_import_methods( + %args, + install => [qw(import unimport init_meta)] + ); } sub build_import_methods { @@ -49,16 +48,28 @@ sub build_import_methods { } ); + my %methods; # $args{_export_to_main} exists for backwards compat, because # Moose::Util::TypeConstraints did export to main (unlike Moose & # Moose::Role). - my $import = $class->_make_import_sub( $exporting_package, $exporter, - \@exports_from, $args{_export_to_main} ); + $methods{import} = $class->_make_import_sub( $exporting_package, + $exporter, \@exports_from, $args{_export_to_main} ); - my $unimport = $class->_make_unimport_sub( $exporting_package, $exports, - $is_removable, $export_recorder ); + $methods{unimport} = $class->_make_unimport_sub( $exporting_package, + $exports, $is_removable, $export_recorder ); + + $methods{init_meta} = $class->_make_init_meta( $exporting_package, + \%args ); + + my $package = Class::MOP::Package->initialize($exporting_package); + for my $to_install (@{ $args{install} || [] }) { + my $symbol = '&' . $to_install; + next unless $methods{$to_install} + && !$package->has_package_symbol($symbol); + $package->add_package_symbol($symbol, $methods{$to_install}); + } - return ( $import, $unimport ) + return ( $methods{import}, $methods{unimport}, $methods{init_meta} ) } { @@ -531,6 +542,49 @@ sub _remove_keywords { } } +sub _make_init_meta { + shift; + my $class = shift; + my $args = shift; + + my %metaclass_roles; + for my $role (map { "${_}_roles" } + qw(metaclass + attribute_metaclass + method_metaclass + wrapped_method_metaclass + instance_metaclass + constructor_class + destructor_class + error_class + application_to_class_class + application_to_role_class + application_to_instance_class)) { + $metaclass_roles{$role} = $args->{$role} if exists $args->{$role}; + } + + my %base_class_roles; + %base_class_roles = (roles => $args->{base_class_roles}) + if exists $args->{base_class_roles}; + + return unless %metaclass_roles || %base_class_roles; + + return sub { + shift; + my %options = @_; + return unless Class::MOP::class_of($options{for_class}); + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => $options{for_class}, + %metaclass_roles, + ); + Moose::Util::MetaRole::apply_base_class_roles( + for_class => $options{for_class}, + %base_class_roles, + ) if Class::MOP::class_of($options{for_class})->isa('Moose::Meta::Class'); + return Class::MOP::class_of($options{for_class}); + }; +} + sub import { strict->import; warnings->import; @@ -579,8 +633,9 @@ Moose::Exporter - make an import() and unimport() just like Moose.pm =head1 DESCRIPTION This module encapsulates the exporting of sugar functions in a -C-like manner. It does this by building custom C and -C methods for your module, based on a spec you provide. +C-like manner. It does this by building custom C, +C, and optionally C methods for your module, +based on a spec you provide. It also lets you "stack" Moose-alike modules so you can export Moose's sugar as well as your own, along with sugar from any random @@ -598,14 +653,23 @@ This module provides two public methods: =item B<< Moose::Exporter->setup_import_methods(...) >> -When you call this method, C build custom C -and C methods for your module. The import method will export -the functions you specify, and you can also tell it to export -functions exported by some other module (like C). +When you call this method, C builds custom C, +C, and C methods for your module. The C +method will export the functions you specify, and you can also tell it +to export functions exported by some other module (like C). The C method cleans the callers namespace of all the exported functions. +The C method will be generated if any parameters for +L are passed to C (see +below). It will handle passing along the required traits to +C and C as needed. + +Note that if any of these methods already exist, they will not be +overridden, you will have to use C to get the +coderef that would be installed. + This method accepts the following parameters: =over 8 @@ -643,9 +707,23 @@ when C is called. =back +Any of the C<*_roles> options for +C are also valid here, +and C will be passed along to the C parameter +of C. + =item B<< Moose::Exporter->build_import_methods(...) >> -Returns two code refs, one for import and one for unimport. +Returns two or three code refs, one for C, one for +C, and optionally one for C, if the appropriate +options are passed in. + +Accepts the additional C option, which accepts an arrayref of +method names to install into your exporter (C, C, +and C). Calling C is equivalent to +calling C with +C<< install => [qw(import unimport init_meta)] >> (except that it +doesn't also return the methods). Used by C. @@ -654,10 +732,12 @@ Used by C. =head1 IMPORTING AND init_meta If you want to set an alternative base object class or metaclass -class, simply define an C method in your class. The -C method that C generates for you will call -this method (if it exists). It will always pass the caller to this -method via the C parameter. +class, see above for how to pass options to L +through the options to C. If you want to do +something not supported by this, simply define an C method +in your class. The C method that C generates +for you will call this method (if it exists). It will always pass the +caller to this method via the C parameter. Most of the time, your C method will probably just call C<< Moose->init_meta >> to do the real work: @@ -667,6 +747,10 @@ Moose->init_meta >> to do the real work: return Moose->init_meta( @_, metaclass => 'My::Metaclass' ); } +Keep in mind that C will return an C +method for you, which you can also call from within your custom +C. + =head1 METACLASS TRAITS The C method generated by C will allow the diff --git a/lib/Moose/Util/MetaRole.pm b/lib/Moose/Util/MetaRole.pm index a713d11..9169727 100644 --- a/lib/Moose/Util/MetaRole.pm +++ b/lib/Moose/Util/MetaRole.pm @@ -178,8 +178,8 @@ this when your module is imported, the caller should not have any attributes defined yet. The easiest way to ensure that this happens is to use -L and provide an C method that will be -called when imported. +L, which can generate the appropriate C +method for you, and make sure it is called when imported. =head1 FUNCTIONS diff --git a/t/050_metaclasses/023_easy_init_meta.t b/t/050_metaclasses/023_easy_init_meta.t new file mode 100644 index 0000000..200d734 --- /dev/null +++ b/t/050_metaclasses/023_easy_init_meta.t @@ -0,0 +1,120 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 13; +use Test::Moose qw(does_ok); + +{ + package Foo::Trait::Class; + use Moose::Role; +} + +{ + package Foo::Trait::Attribute; + use Moose::Role; +} + +{ + package Foo::Role::Base; + use Moose::Role; +} + +{ + package Foo::Exporter; + use Moose::Exporter; + + Moose::Exporter->setup_import_methods( + metaclass_roles => ['Foo::Trait::Class'], + attribute_metaclass_roles => ['Foo::Trait::Attribute'], + base_class_roles => ['Foo::Role::Base'], + ); +} + +{ + package Foo; + use Moose; + Foo::Exporter->import; + + has foo => (is => 'ro'); + + ::does_ok(Foo->meta, 'Foo::Trait::Class'); + ::does_ok(Foo->meta->get_attribute('foo'), 'Foo::Trait::Attribute'); + ::does_ok('Foo', 'Foo::Role::Base'); +} + +{ + package Foo::Exporter::WithMoose; + use Moose (); + use Moose::Exporter; + + my ($import, $unimport, $init_meta) = + Moose::Exporter->build_import_methods( + also => 'Moose', + metaclass_roles => ['Foo::Trait::Class'], + attribute_metaclass_roles => ['Foo::Trait::Attribute'], + base_class_roles => ['Foo::Role::Base'], + install => [qw(import unimport)], + ); + + sub init_meta { + my $package = shift; + my %options = @_; + ::pass('custom init_meta was called'); + Moose->init_meta(%options); + return $package->$init_meta(%options); + } +} + +{ + package Foo2; + Foo::Exporter::WithMoose->import; + + has(foo => (is => 'ro')); + + ::isa_ok('Foo2', 'Moose::Object'); + ::isa_ok(Foo2->meta, 'Moose::Meta::Class'); + ::does_ok(Foo2->meta, 'Foo::Trait::Class'); + ::does_ok(Foo2->meta->get_attribute('foo'), 'Foo::Trait::Attribute'); + ::does_ok('Foo2', 'Foo::Role::Base'); +} + +{ + package Foo::Role; + use Moose::Role; + Foo::Exporter->import; + + ::does_ok(Foo::Role->meta, 'Foo::Trait::Class'); +} + +{ + package Foo::Exporter::WithMooseRole; + use Moose::Role (); + use Moose::Exporter; + + my ($import, $unimport, $init_meta) = + Moose::Exporter->build_import_methods( + also => 'Moose::Role', + metaclass_roles => ['Foo::Trait::Class'], + attribute_metaclass_roles => ['Foo::Trait::Attribute'], + base_class_roles => ['Foo::Role::Base'], + install => [qw(import unimport)], + ); + + sub init_meta { + my $package = shift; + my %options = @_; + ::pass('custom init_meta was called'); + Moose::Role->init_meta(%options); + return $package->$init_meta(%options); + } +} + +{ + package Foo2::Role; + Foo::Exporter::WithMooseRole->import; + + ::isa_ok(Foo2::Role->meta, 'Moose::Meta::Role'); + ::does_ok(Foo2::Role->meta, 'Foo::Trait::Class'); +}