File Coverage

blib/lib/KelpX/AppBuilder.pm
Criterion Covered Total %
statement 63 140 45.0
branch 4 30 13.3
condition 6 9 66.6
subroutine 17 25 68.0
pod 0 3 0.0
total 90 207 43.4


line stmt bran cond sub pod time code
1             package KelpX::AppBuilder;
2              
3 2     2   26038 use 5.010;
  2         5  
4 2     2   6 use warnings;
  2         2  
  2         37  
5 2     2   6 use strict;
  2         7  
  2         28  
6 2     2   725 use KelpX::AppBuilder::Object;
  2         2  
  2         51  
7 2     2   800 use Module::Find 'useall';
  2         1671  
  2         91  
8 2     2   816 use File::ShareDir 'module_dir';
  2         9061  
  2         101  
9 2     2   771 use Import::Into;
  2         3800  
  2         43  
10 2     2   750 use Kelp::Base;
  2         973  
  2         8  
11              
12             our $VERSION = '0.005';
13              
14             sub import {
15 2     2   13 my ($me, @opts) = @_;
16 2         3 my $class = caller;
17             {
18 2     2   310 no strict 'refs';
  2         2  
  2         211  
  2         3  
19 2     2   867 eval "use Kelp::Routes";
  2         27540  
  2         14  
  2         103  
20 2         36 Kelp::Base->import::into($class, 'Kelp');
21 2     0   36879 *{"Kelp::Routes::kelpx_appbuilder"} = sub { KelpX::AppBuilder::Object->new(shift); };
  2         10  
  0         0  
22             }
23 2 100 66     12 if (@opts and $opts[0] eq 'Base') {
24              
25 1         6 my @controllers = useall "${class}::Controller";
26             {
27 2     2   7 no strict 'refs';
  2         2  
  2         149  
28 1         2 for my $c (@controllers) {
29 0 0       0 eval "use $c" unless scalar keys %{"${c}::"};
  0         0  
30 0         0 say "=> Loaded controller $c";
31             }
32             }
33              
34             {
35 2     2   6 no strict 'refs';
  2         2  
  2         329  
  1         204  
  1         2  
36 1         5 *{"${class}::build"} = sub {
37 0     0   0 my ($self) = @_;
38 0         0 my $r = $self->routes;
39              
40 0         0 my $maps = $class->maps;
41 0         0 for my $method (keys %$maps) {
42 0         0 $r->add($method, $maps->{$method});
43             }
44 1         4 };
45             }
46             }
47              
48 2 50 66     10 if (@opts and $opts[0] eq 'Config') {
49 0 0       0 if (scalar @opts > 1) {
50 0         0 my $mod = $opts[1];
51             {
52 2     2   9 no strict 'refs';
  2         2  
  2         150  
  0         0  
53 0 0       0 eval "use $mod" unless scalar keys %{"${mod}::"};
  0         0  
54             }
55 0 0       0 if ($@) {
56 0         0 die "[error] Could not load base module $mod into config: $@\n";
57             }
58              
59             {
60 2     2   7 no strict 'refs';
  2         2  
  2         334  
  0         0  
61 0         0 my $hsh;
62 0         0 my $con = "${mod}::Config";
63 0         0 eval "use $con";
64 0 0       0 if ($@) { die "(!) Could not load config module '$con': $@"; $hsh = {}; }
  0         0  
  0         0  
65 0         0 $hsh = $con->config();
66 0     0   0 *{"${class}::base_path"} = sub { return module_dir($mod) };
  0         0  
  0         0  
67 0     0   0 *{"${class}::base_config"} = sub { return $hsh; };
  0         0  
  0         0  
68             }
69              
70             }
71             else {
72 0         0 die "[error] Config import option expects a base app name\n";
73             }
74             }
75              
76 2 50 66     1084 if (@opts and $opts[0] eq 'BaseConfig') {
77 0 0         if (scalar @opts > 1) {
78 0           my $mod = $opts[1];
79             {
80 2     2   8 no strict 'refs';
  2         2  
  2         638  
  0            
81 0           eval "use $mod";
82 0 0         if ($@) { die "[error] Could not load base mod: ${mod}\n"; }
  0            
83            
84 0     0     *{"${class}::base_path"} = sub { return module_dir($mod); };
  0            
  0            
85             }
86             }
87             else {
88 0           die "[error] BaseConfig import option expects base app name\n";
89             }
90             }
91             }
92              
93             sub new {
94 0     0 0   my ($class, $base) = @_;
95 0           my $self = { name => $base };
96 0           eval "use $base";
97 0 0         if ($@) {
98 0           print STDERR "[error] Failed to open $base: $@\n";
99 0           exit 5;
100             }
101              
102 0           my @controllers = useall "${base}::Controller";
103             eval "use $_"
104 0           for @controllers;
105              
106              
107 0           return bless $self, $class;
108             }
109              
110             sub load_controllers {
111 0     0 0   my ($self, @controllers) = @_;
112 0           for my $c (@controllers) {
113 0           eval "use $self->{name}::Controller::$c";
114 0 0         if ($@) {
115 0           die "[error] Could not load controller $c: $@\n";
116             }
117             }
118              
119 0           return $self;
120             }
121              
122             sub add_maps {
123 0     0 0   my ($self, $r) = @_;
124             {
125 0           my $class = caller;
  0            
126 2     2   9 no strict 'refs';
  2         1  
  2         467  
127              
128 0           my $mod = $self->{name};
129 0 0         unless ($mod->can('maps')) {
130 0           print STDERR "Base mod $mod is missing 'maps' method\n";
131 0           exit 3;
132             }
133              
134 0           my @no_import = qw(new build import);
135 0           foreach my $method (keys %{"${mod}::"}) {
  0            
136 0           *{"${class}::${method}"} = *{"${mod}::${method}"}
  0            
137 0 0         unless grep { $_ eq $method } @no_import;
  0            
138             }
139 0           my $maps = $mod->maps;
140 0           foreach my $path (keys %$maps) {
141 0 0         if ($path eq 'auto') {
142 0           my $root = "${mod}::Controller::Root";
143 0           $r->add('/:page' => { to => $root->can('auto'), bridge => 1 });
144 0           next;
145             }
146              
147 0           $r->add($path, $maps->{$path});
148             }
149              
150             }
151             }
152              
153             =head1 NAME
154              
155             KelpX::AppBuilder - Create re-usable apps with Kelp
156              
157             =head1 SYNOPSIS
158              
159             KelpX::AppBuilder makes it trivial to reuse your entire route map and views in an entirely new Kelp application. You create a base app, which can still be run normally, and from there you can start a new project and reuse everything from your base app without duplicating things.
160              
161             =head1 USAGE
162              
163             =head2 Create a base application
164              
165             This launches your main application, allowing you to attach other ones onto it
166              
167             package BaseApp;
168            
169             use KelpX::AppBuilder;
170              
171             sub build {
172             my ($self) = @_;
173             my $routes = $self->routes;
174              
175             # The only thing we need to do is tell KelpX::AppBuilder what
176             # apps we want to load. Their routes will be added onto BaseApps.
177              
178             $r->kelpx_appbuilder->apps(
179             'TestApp',
180             'TestApp2'
181             );
182              
183             # Then load the main ones as normal
184              
185             $r->add('/' => BaseApp::Controller::Root->can('index'));
186             $r->add('/login' => BaseApp::Controller::Auth->can('login'));
187             $r->add('/accounts/manage/:id' => {
188             to => BaseApp::Controller::Accounts->can('manage'),
189             bridge => 1
190             });
191             $r->add('/accounts/manage/:id/view', BaseApp::Controller::Accounts->can('view'));
192             }
193              
194             1;
195              
196             =head2 Creating an app for your base
197              
198             We'll call our new app 'TestApp' (original, eh?).
199             All your app really needs to provide is a function called C. This should
200             return a hash reference of your routes.
201             Don't forget to include the absolute path to your controllers (ie: Using the + symbol)
202              
203             package TestApp;
204              
205             use KelpX::AppBuilder;
206              
207             sub maps {
208             {
209             '/testapp/welcome', '+TestApp::Controller::Root::welcome'
210             }
211             }
212              
213             1;
214              
215             And that's all there is to it.
216              
217             =head2 Using templates from apps
218              
219             One thing you're probably going to want to do is use something like Template::Toolkit to process
220             your views in apps that aren't the base. Fortunately C will deploy
221             C from L for you, so in your controllers something like this could happen:
222              
223             package TestApp::Controller::Root;
224              
225             use KelpX::AppBuilder::Utils;
226              
227             # create some way to access the view path globally
228             # so you don't have to keep writing it
229             sub view_path { module_dir('TestApp') . '/views/' }
230              
231             sub index {
232             my ($self) = @_;
233             $self->template(view_path() . 'index.tt');
234             }
235              
236             So now when the index method is called from TestApp, it'll search C for its
237             templates.
238              
239             This is probably your best option for now, as KelpX::AppBuilder does not have a safe way to load app
240             configuration just yet (working on it!).
241              
242             =head1 PLEASE NOTE
243              
244             This module is still a work in progress, so I would advise against using KelpX::AppBuilder in a production environment. I'm still looking at ways to make KelpX::AppBuilder more user friendly, but unfortunately reusing an application is not a simple process :-)
245              
246             =head1 AUTHOR
247              
248             Brad Haywood
249              
250             =head1 LICENSE
251              
252             You may distribute this code under the same terms as Perl itself.
253              
254             =cut
255             1;