File Coverage

blib/lib/Devel/InheritNamespace.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1              
2             package Devel::InheritNamespace;
3 2     2   2500 use Moose;
  0            
  0            
4             use Module::Pluggable::Object;
5             use namespace::clean -except => qw(meta);
6              
7             our $VERSION = '0.00002';
8              
9             has search_options => (
10             is => 'ro',
11             isa => 'HashRef',
12             predicate => 'has_search_options'
13             );
14            
15             has on_class_found => (
16             is => 'ro',
17             isa => 'CodeRef',
18             predicate => 'has_on_class_found',
19             );
20              
21             has except => (
22             is => 'ro',
23             isa => 'RegexpRef',
24             lazy_build => 1,
25             );
26              
27             sub _build_except {
28             return qr/::SUPER$/;
29             }
30              
31             # from a given list of namespaces, load everything
32             # however, if names clash, the first one to be loaded wins
33              
34             sub search_components_in_namespace {
35             my ($self, $namespace) = @_;
36              
37             my @search_path = ($namespace);
38             my %config;
39             if ($self->has_search_options) {
40             %config = %{ $self->search_options };
41             }
42              
43             my $locator = Module::Pluggable::Object->new(
44             %config,
45             search_path => [ @search_path ],
46             );
47              
48             my @comps;
49             my $except = $self->except;
50             if ($except) {
51             @comps = sort grep { !/$except/ } $locator->plugins;
52             } else {
53             @comps = sort $locator->plugins;
54             }
55              
56             return @comps;
57             }
58              
59              
60             sub all_modules {
61             my ($self, @namespaces) = @_;
62              
63             my @comps;
64             my $main_namespace = $namespaces[0];
65             foreach my $namespace (@namespaces) {
66             push @comps,
67             map {
68             [ $namespace, $_ ]
69             }
70             $self->search_components_in_namespace( $namespace );
71             }
72              
73             my %comps;
74             foreach my $comp (@comps) {
75             my ($comp_namespace, $comp_class) = @$comp;
76              
77             my $is_virtual;
78             my $base_class;
79              
80             if ($comp_namespace eq $main_namespace ) {
81             if (! Class::MOP::is_class_loaded($comp_class)) {
82             Class::MOP::load_class($comp_class);
83             }
84             } else {
85             $base_class = $comp_class;
86              
87             # see if we can make a subclass out of it
88             $comp_class =~ s/^$comp_namespace/$main_namespace/;
89              
90             next if $comps{ $comp_class };
91             eval { Class::MOP::load_class($comp_class) };
92             if (my $e = $@) {
93             if ($e =~ /Can't locate/) {
94             # if the module is NOT found in the current app ($class),
95             # then we build a virtual component. But don't do this
96             # if $base_class is a role
97             Class::MOP::load_class($base_class);
98             next if $base_class->can('meta') && $base_class->meta->isa('Moose::Meta::Role');
99              
100             my $meta = Moose::Meta::Class->create(
101             $comp_class => ( superclasses => [ $base_class ] )
102             );
103             $is_virtual = 1;
104             } else {
105             confess "Failed to load class $comp_class: $e";
106             }
107             }
108             }
109             $comps{ $comp_class } = {
110             is_virtual => $is_virtual,
111             base_class => $base_class
112             };
113              
114             if ($self->has_on_class_found) {
115             $self->on_class_found->( $comp_class );
116             }
117             }
118             return \%comps;
119             }
120              
121             1;
122              
123             __END__
124              
125             =head1 NAME
126              
127             Devel::InheritNamespace - Inherit An Entire Namespace
128              
129             =head1 SYNOPSIS
130              
131             use Devel::InheritNamespace;
132              
133             my $din = Devel::InheritNamespace->new(
134             on_class_found => sub { ... },
135             );
136             my @modules =
137             $din->all_modules( 'MyApp', 'Parent::Namespace1', 'Parent::Namespace2' );
138              
139             =head1 DESCRIPTION
140              
141             WARNING: YMMV using this module.
142              
143             This module allows you to dynamically "inherit" an entire namespace.
144              
145             For example, suppose you have a set of packages under MyApp::Base:
146              
147             MyApp::Base::Foo
148             MyApp::Base::Bar
149             MyApp::Base::Baz
150              
151             Then some time later you start writing MyApp::Extend.
152             You want to reuse MyApp::Base::Foo and MyApp::Base::Bar by subclassing
153             (because somehow the base namespace matters -- say, in Catalyst), but
154             you want to put a little customization for MyApp::Base::Baz
155              
156             Normally you achieve this by manually creating MyApp::Extended:: modules:
157              
158             # in MyApp/Extended/Foo.pm
159             package MyApp::Extended::Foo;
160             use Moose;
161             extends 'MyApp::Base::Foo';
162              
163             # in MyApp/Extended/Bar.pm
164             package MyApp::Extended::Bar;
165             use Moose;
166             extends 'MyApp::Base::Bar';
167              
168             # in MyApp/Extended/Baz.pm
169             package MyApp::Extended::Baz;
170             use Moose;
171             extends 'MyApp::Base::Baz';
172              
173             ... whatever customization you need ...
174              
175             This is okay for a small number of modules, or if you are only doing this once
176             or twice. But perhaps you have tens of these modules, or maybe you do this
177             on every new project you create to inherit from a base applicatin set.
178              
179             In that case you can use Devel::InheritNamespace.
180              
181             =head1 METHODS
182              
183             =head2 C<< $class->new(%options) >>
184              
185             Constructs a new Devel::InheritNamespace instance. You may pass the following
186             options:
187              
188             =over 4
189              
190             =item except
191              
192             Regular expression to stop certain modules to be included in the search list.
193             Note: This option will probably be deleted in the future releases: see
194             C<search_options> and Module::Pluggable for a way to achieve this.
195              
196             =item on_class_found
197              
198             Callback that gets called when a new class was loaded.
199              
200             =item search_options
201              
202             Extra arguments to pass to Module::Pluggable::Object to search for modules.
203              
204             =back
205              
206             =head2 C<< $self->all_modules( $main_namespace, @namespaces_to_inherit ) >>;
207              
208             Loads modules based on the following heuristics:
209              
210             1. Search all modules in $main_namespace using Module::Pluggable.
211             2. Load those modules
212             3. Repease searching in namespaces declared in the @namespaces_to_inherit
213             4. Check if the corresponding module in the $main_namespace exists.
214             (we basically do $class =~ s/^$current_namespace/$main_namespace/)
215             5. If the module is already loaded, skip and check the module
216             6. If the module has not been loaded, dynamically create a module in
217             the $main_namespace, inheriting from the original one.
218             7. Repeat above for all namespaces.
219              
220             =head1 TODO
221              
222             Documentation. Samples. Tests.
223              
224             =head1 AUTHOR
225              
226             Daisuke Maki C<< <daisuke@endeworks.jp> >>
227              
228             =head1 LICENSE
229              
230             This program is free software; you can redistribute it and/or modify it
231             under the same terms as Perl itself.
232              
233             See http://www.perl.com/perl/misc/Artistic.html
234              
235             =cut
236