Checking in changes prior to tagging of version 0.93.
[gitmo/Mouse.git] / lib / Mouse / Exporter.pm
CommitLineData
db9fc237 1package Mouse::Exporter;
2use strict;
3use warnings;
08ee26f4 4use Carp ();
db9fc237 5
db9fc237 6my %SPEC;
7
57633577 8my $strict_bits;
8a3fa349 9my $warnings_extra_bits;
dca81269 10BEGIN{
11 $strict_bits = strict::bits(qw(subs refs vars));
12 $warnings_extra_bits = warnings::bits(FATAL => 'recursion');
13}
8a3fa349 14
0eb86915 15# it must be "require", because Mouse::Util depends on Mouse::Exporter,
16# which depends on Mouse::Util::import()
17require Mouse::Util;
18
1bd3c83e 19sub import{
8a3fa349 20 # strict->import;
21 $^H |= $strict_bits;
22 # warnings->import('all', FATAL => 'recursion');
23 ${^WARNING_BITS} |= $warnings::Bits{all};
24 ${^WARNING_BITS} |= $warnings_extra_bits;
1bd3c83e 25 return;
26}
27
0eb86915 28
db9fc237 29sub setup_import_methods{
30 my($class, %args) = @_;
31
32 my $exporting_package = $args{exporting_package} ||= caller();
33
e6dc493d 34 my($import, $unimport) = $class->build_import_methods(%args);
35
1194aede 36 Mouse::Util::install_subroutines($exporting_package,
37 import => $import,
38 unimport => $unimport,
39
40 export_to_level => sub {
41 my($package, $level, undef, @args) = @_; # the third argument is redundant
42 $package->import({ into_level => $level + 1 }, @args);
43 },
44 export => sub {
45 my($package, $into, @args) = @_;
46 $package->import({ into => $into }, @args);
47 },
48 );
e6dc493d 49 return;
50}
51
52sub build_import_methods{
230dd14a 53 my($self, %args) = @_;
e6dc493d 54
55 my $exporting_package = $args{exporting_package} ||= caller();
56
db9fc237 57 $SPEC{$exporting_package} = \%args;
58
59 # canonicalize args
60 my @export_from;
61 if($args{also}){
62 my %seen;
63 my @stack = ($exporting_package);
64
65 while(my $current = shift @stack){
66 push @export_from, $current;
67
68 my $also = $SPEC{$current}{also} or next;
0eb86915 69 push @stack, grep{ !$seen{$_}++ } ref($also) ? @{ $also } : $also;
db9fc237 70 }
71 }
72 else{
73 @export_from = ($exporting_package);
74 }
75
72dc2c9d 76 my %exports;
77 my @removables;
78 my @all;
79
80 my @init_meta_methods;
81
82 foreach my $package(@export_from){
83 my $spec = $SPEC{$package} or next;
1ff34b4c 84
72dc2c9d 85 if(my $as_is = $spec->{as_is}){
86 foreach my $thingy (@{$as_is}){
87 my($code_package, $code_name, $code);
88
89 if(ref($thingy)){
90 $code = $thingy;
91 ($code_package, $code_name) = Mouse::Util::get_code_info($code);
92 }
93 else{
72dc2c9d 94 $code_package = $package;
95 $code_name = $thingy;
1194aede 96 no strict 'refs';
72dc2c9d 97 $code = \&{ $code_package . '::' . $code_name };
98 }
99
100 push @all, $code_name;
101 $exports{$code_name} = $code;
102 if($code_package eq $package){
103 push @removables, $code_name;
1ff34b4c 104 }
105 }
db9fc237 106 }
1ff34b4c 107
72dc2c9d 108 if(my $init_meta = $package->can('init_meta')){
109 if(!grep{ $_ == $init_meta } @init_meta_methods){
110 push @init_meta_methods, $init_meta;
1ff34b4c 111 }
1ff34b4c 112 }
72dc2c9d 113 }
114 $args{EXPORTS} = \%exports;
115 $args{REMOVABLES} = \@removables;
1ff34b4c 116
54bc3ef2 117 $args{groups}{all} ||= \@all;
72dc2c9d 118
119 if(my $default_list = $args{groups}{default}){
120 my %default;
121 foreach my $keyword(@{$default_list}){
122 $default{$keyword} = $exports{$keyword}
08ee26f4 123 || Carp::confess(qq{The $exporting_package package does not export "$keyword"});
1ff34b4c 124 }
72dc2c9d 125 $args{DEFAULT} = \%default;
126 }
127 else{
128 $args{groups}{default} ||= \@all;
129 $args{DEFAULT} = $args{EXPORTS};
130 }
131
132 if(@init_meta_methods){
133 $args{INIT_META} = \@init_meta_methods;
db9fc237 134 }
135
e6dc493d 136 return (\&do_import, \&do_unimport);
db9fc237 137}
138
db9fc237 139# the entity of general import()
140sub do_import {
1ff34b4c 141 my($package, @args) = @_;
db9fc237 142
1ff34b4c 143 my $spec = $SPEC{$package}
08ee26f4 144 || Carp::confess("The package $package package does not use Mouse::Exporter");
db9fc237 145
146 my $into = _get_caller_package(ref($args[0]) ? shift @args : undef);
147
148 my @exports;
45bbec05 149 my @traits;
8cbcbb47 150
45bbec05 151 while(@args){
152 my $arg = shift @args;
1ff34b4c 153 if($arg =~ s/^-//){
45bbec05 154 if($arg eq 'traits'){
f12892e5 155 push @traits, ref($args[0]) ? @{shift(@args)} : shift(@args);
45bbec05 156 }
157 else {
158 Mouse::Util::not_supported("-$arg");
159 }
1ff34b4c 160 }
161 elsif($arg =~ s/^://){
1bd3c83e 162 my $group = $spec->{groups}{$arg}
08ee26f4 163 || Carp::confess(qq{The $package package does not export the group "$arg"});
db9fc237 164 push @exports, @{$group};
165 }
166 else{
167 push @exports, $arg;
168 }
169 }
170
4bef84ef 171 # strict->import;
172 $^H |= $strict_bits;
8a3fa349 173 # warnings->import('all', FATAL => 'recursion');
174 ${^WARNING_BITS} |= $warnings::Bits{all};
175 ${^WARNING_BITS} |= $warnings_extra_bits;
db9fc237 176
1ff34b4c 177 if($spec->{INIT_META}){
45bbec05 178 my $meta;
1ff34b4c 179 foreach my $init_meta(@{$spec->{INIT_META}}){
134f7bcb 180 $meta = $package->$init_meta(for_class => $into);
1ff34b4c 181 }
db9fc237 182
45bbec05 183 if(@traits){
184 my $type = (split /::/, ref $meta)[-1]; # e.g. "Class" for "My::Meta::Class"
54bc3ef2 185 @traits = map{
186 ref($_)
187 ? $_
188 : Mouse::Util::resolve_metaclass_alias($type => $_, trait => 1)
189 } @traits;
45bbec05 190
45bbec05 191 require Mouse::Util::MetaRole;
3d7c6ec9 192 Mouse::Util::MetaRole::apply_metaroles(
dca81269 193 for => $into,
3d7c6ec9 194 Mouse::Util::is_a_metarole($into->meta)
195 ? (role_metaroles => { role => \@traits })
196 : (class_metaroles => { class => \@traits }),
45bbec05 197 );
198 }
db9fc237 199 }
f12892e5 200 elsif(@traits){
201 Carp::confess("Cannot provide traits when $package does not have an init_meta() method");
202 }
db9fc237 203
1ff34b4c 204 if(@exports){
1194aede 205 my @export_table;
1ff34b4c 206 foreach my $keyword(@exports){
1194aede 207 push @export_table,
208 $keyword => ($spec->{EXPORTS}{$keyword}
08ee26f4 209 || Carp::confess(qq{The $package package does not export "$keyword"})
1194aede 210 );
1ff34b4c 211 }
1194aede 212 Mouse::Util::install_subroutines($into, @export_table);
1ff34b4c 213 }
214 else{
1194aede 215 Mouse::Util::install_subroutines($into, %{$spec->{DEFAULT}});
db9fc237 216 }
217 return;
218}
219
220# the entity of general unimport()
221sub do_unimport {
1ff34b4c 222 my($package, $arg) = @_;
db9fc237 223
1ff34b4c 224 my $spec = $SPEC{$package}
08ee26f4 225 || Carp::confess("The package $package does not use Mouse::Exporter");
db9fc237 226
227 my $from = _get_caller_package($arg);
228
229 my $stash = do{
230 no strict 'refs';
231 \%{$from . '::'}
232 };
233
234 for my $keyword (@{ $spec->{REMOVABLES} }) {
21df8e42 235 next if !exists $stash->{$keyword};
4ee3190e 236 my $gv = \$stash->{$keyword};
dca81269 237
238 # remove what is from us
239 if(ref($gv) eq 'GLOB' && *{$gv}{CODE} == $spec->{EXPORTS}{$keyword}){
4ee3190e 240 delete $stash->{$keyword};
241 }
db9fc237 242 }
243 return;
244}
245
246sub _get_caller_package {
247 my($arg) = @_;
248
7ad9df77 249 # We need one extra level because it's called by import so there's a layer
250 # of indirection
db9fc237 251 if(ref $arg){
252 return defined($arg->{into}) ? $arg->{into}
7ad9df77 253 : defined($arg->{into_level}) ? scalar caller(1 + $arg->{into_level})
254 : scalar caller(1);
db9fc237 255 }
256 else{
7ad9df77 257 return scalar caller(1);
db9fc237 258 }
259}
260
2611;
db9fc237 262__END__
263
264=head1 NAME
265
8cbcbb47 266Mouse::Exporter - make an import() and unimport() just like Mouse.pm
db9fc237 267
a25ca8d6 268=head1 VERSION
269
6336eb18 270This document describes Mouse version 0.93
a25ca8d6 271
db9fc237 272=head1 SYNOPSIS
273
7ad9df77 274 package MyApp::Mouse;
275
276 use Mouse ();
277 use Mouse::Exporter;
278
279 Mouse::Exporter->setup_import_methods(
54bc3ef2 280 as_is => [ 'has_rw', 'other_sugar', \&Some::Random::thing ],
281 also => 'Mouse',
7ad9df77 282 );
283
8cbcbb47 284 sub has_rw {
7ad9df77 285 my $meta = caller->meta;
286 my ( $name, %options ) = @_;
287 $meta->add_attribute(
288 $name,
289 is => 'rw',
290 %options,
291 );
292 }
293
294 # then later ...
295 package MyApp::User;
296
297 use MyApp::Mouse;
298
299 has 'name';
300 has_rw 'size';
301 thing;
302
8cbcbb47 303 no MyApp::Mouse;
db9fc237 304
305=head1 DESCRIPTION
306
7ad9df77 307This module encapsulates the exporting of sugar functions in a
308C<Mouse.pm>-like manner. It does this by building custom C<import>,
309C<unimport> methods for your module, based on a spec you provide.
8cbcbb47 310
311Note that C<Mouse::Exporter> does not provide the C<with_meta> option,
312but you can easily get the metaclass by C<< caller->meta >> as L</SYNOPSIS> shows.
db9fc237 313
e6dc493d 314=head1 METHODS
315
316=head2 C<< setup_import_methods( ARGS ) >>
317
318=head2 C<< build_import_methods( ARGS ) -> (\&import, \&unimport) >>
319
db9fc237 320=head1 SEE ALSO
321
322L<Moose::Exporter>
323
5ea1528f 324=cut
325