File Coverage

blib/lib/CatalystX/AppBuilder.pm
Criterion Covered Total %
statement 6 76 7.8
branch 0 32 0.0
condition 0 3 0.0
subroutine 2 10 20.0
pod 2 3 66.6
total 10 124 8.0


line stmt bran cond sub pod time code
1             package CatalystX::AppBuilder;
2 1     1   847 use Moose;
  1         300307  
  1         4  
3 1     1   4467 use namespace::clean -except => qw(meta);
  1         1  
  1         8  
4              
5             our $VERSION = '0.00011';
6              
7             has appname => (
8             is => 'ro',
9             isa => 'Str',
10             required => 1,
11             );
12              
13             has appmeta => (
14             init_arg => undef,
15             is => 'ro',
16             isa => 'Moose::Meta::Class',
17             lazy_build => 1
18             );
19              
20             has debug => (
21             is => 'ro',
22             isa => 'Bool',
23             default => 0,
24             );
25              
26             has version => (
27             is => 'ro',
28             isa => 'Str',
29             required => 1,
30             lazy_build => 1,
31             );
32              
33             has superclasses => (
34             is => 'ro',
35             isa => 'Maybe[ArrayRef]',
36             required => 1,
37             lazy_build => 1,
38             );
39              
40             has config => (
41             is => 'ro',
42             isa => 'HashRef',
43             lazy_build => 1,
44             );
45              
46             has plugins => (
47             is => 'ro',
48             isa => 'ArrayRef',
49             lazy_build => 1,
50             );
51              
52 0     0     sub _build_version { '0.00001' }
53 0     0     sub _build_superclasses { [ 'Catalyst' ] }
54             sub _build_config {
55 0     0     my $self = shift;
56 0           my %config = (
57             name => $self->appname,
58             );
59 0           return \%config;
60             }
61              
62             sub _build_plugins {
63 0     0     my $self = shift;
64 0           my @plugins = qw(ConfigLoader);
65 0 0         if ($self->debug) {
66 0           unshift @plugins, '-Debug';
67             }
68 0           return \@plugins;
69             }
70              
71             sub BUILD {
72 0     0 0   my $self = shift;
73              
74 0           my $appname = $self->appname;
75 0           my $meta = Moose::Util::find_meta( $appname );
76 0 0 0       if (! $meta || ! $appname->isa('Catalyst') ) {
77 0 0         if ($self->debug) {
78 0           print STDERR "Defining $appname via " . (blessed $self) . "\n";
79             }
80 0           $meta = Moose::Meta::Class->create(
81             $appname => (
82             version => $self->version,
83             superclasses => $self->superclasses
84             )
85             );
86              
87 0 0         if ($appname->isa('Catalyst')) {
88             # Don't let the base class fool us!
89 0           delete $appname->config->{home};
90 0           delete $appname->config->{root};
91             }
92             # Fugly, I know, but we need to load Catalyst in the app's namespace
93             # for many things to take effect.
94 0           eval <<" EOCODE";
95             package $appname;
96             use Catalyst;
97             EOCODE
98 0 0         die if $@;
99             }
100 0           return $meta;
101             }
102              
103             sub bootstrap {
104 0     0     my $self = shift;
105 0           my $runsetup = shift;
106 0           my $appclass = $self->appname;
107              
108 0 0         if (! $runsetup) {
109             # newer catalyst now uses Catalyst::ScriptRunner.
110             # run setup if we were explicitly asked for, or we were called from
111             # within Catalyst::ScriptRunner
112 0           my $i = 1;
113 0           $runsetup = 1;
114 0           while (my @caller = caller($i++)) {
115 0           my $package = $caller[0];
116 0           my $sub = $caller[3];
117              
118             # DO NOT run setup if we're being recursively called from
119             # an inherited AppBuilder
120 0 0         if ($package->isa('Class::MOP::Class')) {
    0          
    0          
121 0 0         if ($sub =~ /superclasses$/) {
122 0           $runsetup = 0;
123 0           last;
124             }
125             } elsif ($package->isa('Catalyst::ScriptRunner')) {
126 0           last;
127             } elsif ($package->isa('Catalyst::Restarter')) {
128 0           last;
129             }
130             }
131             }
132              
133 0 0         if ($runsetup) {
134 0           my @plugins;
135             my %plugins;
136 0           foreach my $plugin (@{ $self->plugins }) {
  0            
137 0 0         if ($plugins{$plugin}++) {
138 0           warn "$plugin appears multiple times in the plugin list! Ignoring...";
139             } else {
140 0           push @plugins, $plugin;
141             }
142             }
143              
144 0           $appclass->config( $self->config );
145 0           $appclass->setup( @plugins );
146             }
147             }
148              
149             sub inherited_path_to {
150 0     0 1   my $self = shift;
151              
152             # XXX You have to have built the class
153 0           my $meta = Moose::Util::find_meta($self->appname);
154              
155 0           my @inheritance;
156 0           foreach my $class ($meta->linearized_isa) {
157 0 0         next if ! $class->isa( 'Catalyst' );
158 0 0         next if $class eq 'Catalyst';
159              
160 0           push @inheritance, $class;
161             }
162              
163 0           my @paths = @_;
164             return map {
165 0           my $m = $_;
  0            
166 0           $m =~ s/::/\//g;
167 0           $m .= '.pm';
168 0           my $f = Path::Class::File->new($INC{$m})->parent;
169 0           DESCENT: while ($f) {
170 0           for my $stopper (qw(Makefile.PL Build.PL dist.ini minil.toml)) {
171 0 0         if (-f $f->file($stopper)) {
172 0           $f = $f->subdir(@paths)->stringify;
173 0           last DESCENT;
174             }
175             }
176 0 0         last if $f->stringify eq $f->parent->stringify;
177 0           $f = $f->parent;
178             }
179 0           $f;
180             } @inheritance;
181             }
182              
183             sub app_path_to {
184 0     0 1   my $self = shift;
185              
186 0           return $self->appname->path_to(@_)->stringify;
187             }
188            
189              
190             __PACKAGE__->meta->make_immutable();
191              
192             1;
193              
194              
195             __END__
196              
197             =head1 NAME
198              
199             CatalystX::AppBuilder - Build Your Application Instance Programatically
200              
201             =head1 SYNOPSIS
202              
203             # In MyApp.pm
204             my $builder = CatalystX::AppBuilder->new(
205             appname => 'MyApp',
206             plugins => [ ... ],
207             )
208             $builder->bootstrap();
209              
210             =head1 DESCRIPTION
211              
212             WARNING: YMMV regarding this module.
213              
214             This module gives you a programatic interface to I<configuring> Catalyst
215             applications.
216              
217             The main motivation to write this module is: to write reusable Catalyst
218             appllications. For instance, if you build your MyApp::Base and you wanted to
219             create a new application afterwards that is I<mostly> like MyApp::Base,
220             but slightly tweaked. Perhaps you want to add or remove a plugin or two.
221             Perhaps you want to tweak just a single parameter.
222              
223             Traditionally, your option then was to use catalyst.pl and create another
224             scaffold, and copy/paste the necessary bits, and tweak what you need.
225              
226             After testing several approaches, it proved that the current Catalyst
227             architecture (which is Moose based, but does not allow us to use Moose-ish
228             initialization, since the Catalyst app instance does not materialize until
229             dispatch time) did not allow the type of inheritance behavior we wanted, so
230             we decided to create a builder module around Catalyst to overcome this.
231             Therefore, if/when these obstacles (to us) are gone, this module may
232             simply dissappear from CPAN. You've been warned.
233              
234             =head1 HOW TO USE
235              
236             =head2 DEFINING A CATALYST APP
237              
238             This module is NOT a "just-execute-this-command-and-you-get-catalyst-running"
239             module. For the simple applications, please just follow what the Catalyst
240             manual gives you.
241              
242             However, if you I<really> wanted to, you can define a simple Catalyst
243             app like so:
244              
245             # in MyApp.pm
246             use strict;
247             use CatalystX::AppBuilder;
248            
249             my $builder = CatalystX::AppBuilder->new(
250             debug => 1, # if you want
251             appname => "MyApp",
252             plugins => [ qw(
253             Authentication
254             Session
255             # and others...
256             ) ],
257             config => { ... }
258             );
259              
260             $builder->bootstrap();
261              
262             =head2 DEFINING YOUR CatalystX::AppBuilder SUBCLASS
263              
264             The originally intended approach to using this module is to create a
265             subclass of CatalystX::AppBuilder and configure it to your own needs,
266             and then keep reusing it.
267              
268             To build your own MyApp::Builder, you just need to subclass it:
269              
270             package MyApp::Builder;
271             use Moose;
272              
273             extends 'CatalystX::AppBuilder';
274              
275             Then you will be able to give it defaults to the various configuration
276             parameters:
277              
278             override _build_config => sub {
279             my $config = super(); # Get what CatalystX::AppBuilder gives you
280             $config->{ SomeComponent } = { ... };
281             return $config;
282             };
283              
284             override _build_plugins => sub {
285             my $plugins = super(); # Get what CatalystX::AppBuilder gives you
286              
287             push @$plugins, qw(
288             Unicode
289             Authentication
290             Session
291             Session::Store::File
292             Session::State::Cookie
293             );
294              
295             return $plugins;
296             };
297              
298             Then you can simply do this instead of giving parameters to
299             CatalystX::AppBuilder every time:
300              
301             # in MyApp.pm
302             use MyApp::Builder;
303             MyApp::Builder->new()->bootstrap();
304              
305             =head2 EXTENDING A CATALYST APP USING CatalystX::AppBuilder
306              
307             Once you created your own MyApp::Builder, you can keep inheriting it to
308             create custom Builders which in turn create more custom Catalyst applications:
309              
310             package MyAnotherApp::Builder;
311             use Moose;
312              
313             extends 'MyApp::Builder';
314              
315             override _build_superclasses => sub {
316             return [ 'MyApp' ]
317             }
318              
319             ... do your tweaking ...
320              
321             # in MyAnotherApp.pm
322             use MyAnotherApp::Builder;
323              
324             MyAnotherApp::Builder->new()->bootstrap();
325              
326             Voila, you just reused every inch of Catalyst app that you created via
327             inheritance!
328              
329             =head2 INCLUDING EVERY PATH FROM YOUR INHERITANCE HIERARCHY
330              
331             Components like Catalyst::View::TT, which in turn uses Template Toolkit
332             inside, allows you to include multiple directories to look for the
333             template files.
334              
335             This can be used to recycle the templates that you used in a base application.
336              
337             CatalystX::AppBuilder gives you a couple of tools to easily include
338             paths that are associated with all of the Catalyst applications that are
339             inherited. For example, if you have MyApp::Base and MyApp::Extended,
340             and MyApp::Extended is built using MyApp::Extended::Builder, you can do
341             something like this:
342              
343             package MyApp::Extended::Builder;
344             use Moose;
345              
346             extends 'CatalystX::AppBuilder';
347              
348             override _build_superclasses => sub {
349             return [ 'MyApp::Base' ]
350             };
351              
352             override _build_config => sub {
353             my $self = shift;
354             my $config = super();
355              
356             $config->{'View::TT'}->{INCLUDE_PATH} =
357             [ $self->inherited_path_to('root') ];
358             # Above is equivalent to
359             # [ MyApp::Extended->path_to('root'), MyApp::Base->path_to('root') ]
360             };
361              
362             So now you can refer to some template, and it will first look under the
363             first app, then the base app, thus allowing you to reuse the templates.
364              
365             =head1 ATTRIBUTES
366              
367             =head2 appname
368              
369             The module name of the Catalyst application. Required.
370              
371             =head2 appmeta
372              
373             The metaclass object of the Catalyst application. Users cannot set this.
374              
375             =head2 debug
376              
377             Boolean flag to enable debug output in the application
378              
379             =head2 version
380              
381             The version string to use (probably meaningless...)
382              
383             =head2 superclasses
384              
385             The list of superclasses of the Catalyst application.
386              
387             =head2 config
388              
389             The config hash to give to the Catalyst application.
390              
391             =head2 plugins
392              
393             The list of plugins to give to the Catalyst application.
394              
395             =head1 METHODS
396              
397             =head2 bootstrap($runsetup)
398              
399             Bootstraps the Catalyst app.
400              
401             =head2 inherited_path_to(@pathspec)
402              
403             Calls path_to() on all Catalyst applications in the inheritance tree.
404              
405             =head2 app_path_to(@pathspec);
406              
407             Calls path_to() on the curent Catalyst application.
408              
409             =head1 TODO
410              
411             Documentation. Samples. Tests.
412              
413             =head1 AUTHOR
414              
415             Daisuke Maki C<< <daisuke@endeworks.jp> >>
416              
417             =head1 LICENSE
418              
419             This program is free software; you can redistribute it and/or modify it
420             under the same terms as Perl itself.
421              
422             See http://www.perl.com/perl/misc/Artistic.html
423             =cut