File Coverage

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