Add a regression test
[gitmo/Mouse.git] / lib / Mouse / Exporter.pm
CommitLineData
db9fc237 1package Mouse::Exporter;
2use strict;
3use warnings;
4
5use Carp qw(confess);
6
db9fc237 7my %SPEC;
8
1ff34b4c 9my $strict_bits = strict::bits(qw(subs refs vars));
10
0eb86915 11# it must be "require", because Mouse::Util depends on Mouse::Exporter,
12# which depends on Mouse::Util::import()
13require Mouse::Util;
14
1bd3c83e 15sub import{
16 $^H |= $strict_bits; # strict->import;
17 ${^WARNING_BITS} = $warnings::Bits{all}; # warnings->import;
18 return;
19}
20
0eb86915 21
db9fc237 22sub setup_import_methods{
23 my($class, %args) = @_;
24
25 my $exporting_package = $args{exporting_package} ||= caller();
26
27 $SPEC{$exporting_package} = \%args;
28
29 # canonicalize args
30 my @export_from;
31 if($args{also}){
32 my %seen;
33 my @stack = ($exporting_package);
34
35 while(my $current = shift @stack){
36 push @export_from, $current;
37
38 my $also = $SPEC{$current}{also} or next;
0eb86915 39 push @stack, grep{ !$seen{$_}++ } ref($also) ? @{ $also } : $also;
db9fc237 40 }
41 }
42 else{
43 @export_from = ($exporting_package);
44 }
45
46 {
47 my %exports;
48 my @removables;
4ee3190e 49 my @all;
db9fc237 50
1ff34b4c 51 my @init_meta_methods;
52
db9fc237 53 foreach my $package(@export_from){
54 my $spec = $SPEC{$package} or next;
55
56 if(my $as_is = $spec->{as_is}){
57 foreach my $thingy (@{$as_is}){
4ee3190e 58 my($code_package, $code_name, $code);
db9fc237 59
60 if(ref($thingy)){
db9fc237 61 $code = $thingy;
1bd3c83e 62 ($code_package, $code_name) = Mouse::Util::get_code_info($code);
db9fc237 63 }
64 else{
65 no strict 'refs';
4ee3190e 66 $code_package = $package;
67 $code_name = $thingy;
68 $code = \&{ $code_package . '::' . $code_name };
db9fc237 69 }
70
4ee3190e 71 push @all, $code_name;
72 $exports{$code_name} = $code;
73 if($code_package eq $package){
74 push @removables, $code_name;
75 }
db9fc237 76 }
77 }
1ff34b4c 78
79 if(my $init_meta = $package->can('init_meta')){
80 if(!grep{ $_ == $init_meta } @init_meta_methods){
81 unshift @init_meta_methods, $init_meta;
82 }
83 }
db9fc237 84 }
85 $args{EXPORTS} = \%exports;
86 $args{REMOVABLES} = \@removables;
87
1bd3c83e 88 $args{groups}{all} ||= \@all;
1ff34b4c 89
1bd3c83e 90 if(my $default_list = $args{groups}{default}){
1ff34b4c 91 my %default;
92 foreach my $keyword(@{$default_list}){
93 $default{$keyword} = $exports{$keyword}
94 || confess(qq{The $exporting_package package does not export "$keyword"});
95 }
96 $args{DEFAULT} = \%default;
97 }
98 else{
1bd3c83e 99 $args{groups}{default} ||= \@all;
100 $args{DEFAULT} = $args{EXPORTS};
1ff34b4c 101 }
102
103 if(@init_meta_methods){
104 $args{INIT_META} = \@init_meta_methods;
105 }
db9fc237 106 }
107
108 no strict 'refs';
109
110 *{$exporting_package . '::import'} = \&do_import;
111 *{$exporting_package . '::unimport'} = \&do_unimport;
112
d794b334 113 # for backward compatibility
114
115 *{$exporting_package . '::export_to_level'} = sub{
116 my($package, $level, @args) = @_;
117 do_import($package, { into_level => $level + 1 }, @args);
118 };
119 *{$exporting_package . '::export'} = sub{
120 my($package, $into, @args) = @_;
121 do_import($package, { into => $into }, @args);
122 };
123
db9fc237 124 return;
125}
126
db9fc237 127
128# the entity of general import()
129sub do_import {
1ff34b4c 130 my($package, @args) = @_;
db9fc237 131
1ff34b4c 132 my $spec = $SPEC{$package}
133 || confess("The package $package package does not use Mouse::Exporter");
db9fc237 134
135 my $into = _get_caller_package(ref($args[0]) ? shift @args : undef);
136
137 my @exports;
8cbcbb47 138
db9fc237 139 foreach my $arg(@args){
1ff34b4c 140 if($arg =~ s/^-//){
1bd3c83e 141 Mouse::Util::not_supported("-$arg");
1ff34b4c 142 }
143 elsif($arg =~ s/^://){
1bd3c83e 144 my $group = $spec->{groups}{$arg}
1ff34b4c 145 || confess(qq{The $package package does not export the group "$arg"});
db9fc237 146 push @exports, @{$group};
147 }
148 else{
149 push @exports, $arg;
150 }
151 }
152
1ff34b4c 153 $^H |= $strict_bits; # strict->import;
154 ${^WARNING_BITS} = $warnings::Bits{all}; # warnings->import;
db9fc237 155
0eb86915 156 if($into eq 'main' && !$spec->{_export_to_main}){
1ff34b4c 157 warn qq{$package does not export its sugar to the 'main' package.\n};
db9fc237 158 return;
159 }
160
1ff34b4c 161 if($spec->{INIT_META}){
162 foreach my $init_meta(@{$spec->{INIT_META}}){
163 $into->$init_meta(for_class => $into);
164 }
db9fc237 165
1ff34b4c 166 # _apply_meta_traits($into); # TODO
db9fc237 167 }
168
1ff34b4c 169 if(@exports){
170 foreach my $keyword(@exports){
171 no strict 'refs';
172 *{$into.'::'.$keyword} = $spec->{EXPORTS}{$keyword}
173 || confess(qq{The $package package does not export "$keyword"});
174 }
175 }
176 else{
177 my $default = $spec->{DEFAULT};
178 while(my($keyword, $code) = each %{$default}){
179 no strict 'refs';
180 *{$into.'::'.$keyword} = $code;
181 }
db9fc237 182 }
183 return;
184}
185
186# the entity of general unimport()
187sub do_unimport {
1ff34b4c 188 my($package, $arg) = @_;
db9fc237 189
1ff34b4c 190 my $spec = $SPEC{$package}
191 || confess("The package $package does not use Mouse::Exporter");
db9fc237 192
193 my $from = _get_caller_package($arg);
194
195 my $stash = do{
196 no strict 'refs';
197 \%{$from . '::'}
198 };
199
200 for my $keyword (@{ $spec->{REMOVABLES} }) {
4ee3190e 201 my $gv = \$stash->{$keyword};
202 if(ref($gv) eq 'GLOB' && *{$gv}{CODE} == $spec->{EXPORTS}{$keyword}){ # make sure it is from us
203 delete $stash->{$keyword};
204 }
db9fc237 205 }
206 return;
207}
208
8cbcbb47 209# 1 extra level because it's called by import so there's a layer\r
210# of indirection\r
211sub _LEVEL(){ 1 }
212
db9fc237 213sub _get_caller_package {
214 my($arg) = @_;
215
db9fc237 216 if(ref $arg){
217 return defined($arg->{into}) ? $arg->{into}
8cbcbb47 218 : defined($arg->{into_level}) ? scalar caller(_LEVEL + $arg->{into_level})
219 : scalar caller(_LEVEL);
db9fc237 220 }
221 else{
8cbcbb47 222 return scalar caller(_LEVEL);
db9fc237 223 }
224}
225
2261;
227
228__END__
229
230=head1 NAME
231
8cbcbb47 232Mouse::Exporter - make an import() and unimport() just like Mouse.pm
db9fc237 233
234=head1 SYNOPSIS
235
8cbcbb47 236 package MyApp::Mouse;\r
237\r
238 use Mouse ();\r
239 use Mouse::Exporter;\r
240\r
241 Mouse::Exporter->setup_import_methods(\r
242 as_is => [ 'has_rw', 'other_sugar', \&Some::Random::thing ],\r
243 also => 'Mouse',\r
244 );\r
245\r
246 sub has_rw {
247 my $meta = caller->meta;\r
248 my ( $name, %options ) = @_;\r
249 $meta->add_attribute(\r
250 $name,\r
251 is => 'rw',\r
252 %options,\r
253 );\r
254 }\r
255\r
256 # then later ...\r
257 package MyApp::User;\r
258
259 use MyApp::Mouse;\r
260\r
261 has 'name';\r
262 has_rw 'size';\r
263 thing;\r
264\r
265 no MyApp::Mouse;
db9fc237 266
267=head1 DESCRIPTION
268
8cbcbb47 269This module encapsulates the exporting of sugar functions in a\r
270C<Mouse.pm>-like manner. It does this by building custom C<import>,\r
271C<unimport> methods for your module, based on a spec you provide.\r
272
273Note that C<Mouse::Exporter> does not provide the C<with_meta> option,
274but you can easily get the metaclass by C<< caller->meta >> as L</SYNOPSIS> shows.
db9fc237 275
276=head1 SEE ALSO
277
278L<Moose::Exporter>
279
280=cut