File Coverage

blib/lib/KelpX/AppBuilder.pm
Criterion Covered Total %
statement 56 133 42.1
branch 4 30 13.3
condition 6 9 66.6
subroutine 15 23 65.2
pod 0 3 0.0
total 81 198 40.9


line stmt bran cond sub pod time code
1             package KelpX::AppBuilder;
2              
3 2     2   26108 use 5.010;
  2         4  
4 2     2   6 use warnings;
  2         2  
  2         43  
5 2     2   7 use strict;
  2         4  
  2         30  
6 2     2   731 use KelpX::AppBuilder::Object;
  2         3  
  2         50  
7 2     2   803 use Module::Find 'useall';
  2         1655  
  2         95  
8 2     2   814 use File::ShareDir 'module_dir';
  2         8818  
  2         150  
9              
10             our $VERSION = '0.004';
11              
12             sub import {
13 2     2   15 my ($me, @opts) = @_;
14 2         3 my $class = caller;
15             {
16 2     2   10 no strict 'refs';
  2         2  
  2         130  
  2         1  
17 2     2   979 eval "use Kelp::Routes";
  2         28595  
  2         13  
  2         94  
18 2     0   33 *{"Kelp::Routes::kelpx_appbuilder"} = sub { KelpX::AppBuilder::Object->new(shift); };
  2         8  
  0         0  
19             }
20 2 100 66     11 if (@opts and $opts[0] eq 'Base') {
21              
22 1         3 my @controllers = useall "${class}::Controller";
23             {
24 2     2   6 no strict 'refs';
  2         3  
  2         99  
25 1         2 for my $c (@controllers) {
26 0 0       0 eval "use $c" unless scalar keys %{"${c}::"};
  0         0  
27 0         0 say "=> Loaded controller $c";
28             }
29             }
30              
31             {
32 2     2   6 no strict 'refs';
  2         2  
  2         190  
  1         196  
  1         2  
33 1         4 *{"${class}::build"} = sub {
34 0     0   0 my ($self) = @_;
35 0         0 my $r = $self->routes;
36              
37 0         0 my $maps = $class->maps;
38 0         0 for my $method (keys %$maps) {
39 0         0 $r->add($method, $maps->{$method});
40             }
41 1         3 };
42             }
43             }
44              
45 2 50 66     11 if (@opts and $opts[0] eq 'Config') {
46 0 0       0 if (scalar @opts > 1) {
47 0         0 my $mod = $opts[1];
48             {
49 2     2   8 no strict 'refs';
  2         2  
  2         111  
  0         0  
50 0 0       0 eval "use $mod" unless scalar keys %{"${mod}::"};
  0         0  
51             }
52 0 0       0 if ($@) {
53 0         0 die "[error] Could not load base module $mod into config: $@\n";
54             }
55              
56             {
57 2     2   6 no strict 'refs';
  2         2  
  2         255  
  0         0  
58 0         0 my $hsh;
59 0         0 my $con = "${mod}::Config";
60 0         0 eval "use $con";
61 0 0       0 if ($@) { die "(!) Could not load config module '$con': $@"; $hsh = {}; }
  0         0  
  0         0  
62 0         0 $hsh = $con->config();
63 0     0   0 *{"${class}::base_path"} = sub { return module_dir($mod) };
  0         0  
  0         0  
64 0     0   0 *{"${class}::base_config"} = sub { return $hsh; };
  0         0  
  0         0  
65             }
66              
67             }
68             else {
69 0         0 die "[error] Config import option expects a base app name\n";
70             }
71             }
72              
73 2 50 66     961 if (@opts and $opts[0] eq 'BaseConfig') {
74 0 0         if (scalar @opts > 1) {
75 0           my $mod = $opts[1];
76             {
77 2     2   7 no strict 'refs';
  2         3  
  2         503  
  0            
78 0           eval "use $mod";
79 0 0         if ($@) { die "[error] Could not load base mod: ${mod}\n"; }
  0            
80            
81 0     0     *{"${class}::base_path"} = sub { return module_dir($mod); };
  0            
  0            
82             }
83             }
84             else {
85 0           die "[error] BaseConfig import option expects base app name\n";
86             }
87             }
88             }
89              
90             sub new {
91 0     0 0   my ($class, $base) = @_;
92 0           my $self = { name => $base };
93 0           eval "use $base";
94 0 0         if ($@) {
95 0           print STDERR "[error] Failed to open $base: $@\n";
96 0           exit 5;
97             }
98              
99 0           my @controllers = useall "${base}::Controller";
100             eval "use $_"
101 0           for @controllers;
102              
103              
104 0           return bless $self, $class;
105             }
106              
107             sub load_controllers {
108 0     0 0   my ($self, @controllers) = @_;
109 0           for my $c (@controllers) {
110 0           eval "use $self->{name}::Controller::$c";
111 0 0         if ($@) {
112 0           die "[error] Could not load controller $c: $@\n";
113             }
114             }
115              
116 0           return $self;
117             }
118              
119             sub add_maps {
120 0     0 0   my ($self, $r) = @_;
121             {
122 0           my $class = caller;
  0            
123 2     2   7 no strict 'refs';
  2         2  
  2         354  
124              
125 0           my $mod = $self->{name};
126 0 0         unless ($mod->can('maps')) {
127 0           print STDERR "Base mod $mod is missing 'maps' method\n";
128 0           exit 3;
129             }
130              
131 0           my @no_import = qw(new build import);
132 0           foreach my $method (keys %{"${mod}::"}) {
  0            
133 0           *{"${class}::${method}"} = *{"${mod}::${method}"}
  0            
134 0 0         unless grep { $_ eq $method } @no_import;
  0            
135             }
136 0           my $maps = $mod->maps;
137 0           foreach my $path (keys %$maps) {
138 0 0         if ($path eq 'auto') {
139 0           my $root = "${mod}::Controller::Root";
140 0           $r->add('/:page' => { to => $root->can('auto'), bridge => 1 });
141 0           next;
142             }
143              
144 0           $r->add($path, $maps->{$path});
145             }
146              
147             }
148             }
149              
150             =head1 NAME
151              
152             KelpX::AppBuilder - Create re-usable apps with Kelp
153              
154             =head1 SYNOPSIS
155              
156             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.
157              
158             =head1 USAGE
159              
160             =head2 Create a base application
161              
162             This launches your main application, allowing you to attach other ones onto it
163              
164             package BaseApp;
165            
166             use Kelp::Base 'Kelp';
167             use KelpX::AppBuilder;
168              
169             sub build {
170             my ($self) = @_;
171             my $routes = $self->routes;
172              
173             # The only thing we need to do is tell KelpX::AppBuilder what
174             # apps we want to load. Their routes will be added onto BaseApps.
175              
176             $r->kelpx_appbuilder->apps(
177             'TestApp',
178             'TestApp2'
179             );
180              
181             # Then load the main ones as normal
182              
183             $r->add('/' => BaseApp::Controller::Root->can('index'));
184             $r->add('/login' => BaseApp::Controller::Auth->can('login'));
185             $r->add('/accounts/manage/:id' => {
186             to => BaseApp::Controller::Accounts->can('manage'),
187             bridge => 1
188             });
189             $r->add('/accounts/manage/:id/view', BaseApp::Controller::Accounts->can('view'));
190             }
191              
192             1;
193              
194             =head2 Creating an app for your base
195              
196             We'll call our new app 'TestApp' (original, eh?).
197             All your app really needs to provide is a function called C. This should
198             return a hash reference of your routes.
199             Don't forget to include the absolute path to your controllers (ie: Using the + symbol)
200              
201             package TestApp;
202              
203             use Kelp::Base 'Kelp';
204             use KelpX::AppBuilder;
205              
206             sub maps {
207             {
208             '/testapp/welcome', '+TestApp::Controller::Root::welcome'
209             }
210             }
211              
212             1;
213              
214             And that's all there is to it.
215              
216             =head1 SHARING CONFIG BETWEEN BASEAPP AND ITS CHILDREN
217              
218             You can share config from your base application so you don't have to rewrite stuff you want
219             to reuse. In your child applications C, just add
220              
221             use KelpX::AppBuilder Config => 'BaseApp';
222             return base_config();
223              
224             This will load everything from C. So let's create that.
225              
226             package BaseApp::Config;
227              
228             sub config {
229             return {
230             modules => [qw/Template JSON Logger/],
231             modules_init => {
232              
233             # One log for errors and one for debug
234             Logger => {
235             outputs => [
236             [
237             'File',
238             name => 'debug',
239             filename => 'log/debug.log',
240             min_level => 'debug',
241             mode => '>>',
242             newline => 1,
243             binmode => ":encoding(UTF-8)"
244             ], [
245             'File',
246             name => 'error',
247             filename => 'log/error.log',
248             min_level => 'error',
249             mode => '>>',
250             newline => 1,
251             binmode => ":encoding(UTF-8)"
252             ],
253             ]
254             },
255              
256             # JSON prints pretty
257             JSON => {
258             pretty => 1
259             },
260              
261             # Enable UTF-8 in Template
262             Template => {
263             encoding => 'utf8'
264             }
265             }
266             };
267             }
268              
269             =head1 PLEASE NOTE
270              
271             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 :-)
272              
273             =head1 AUTHOR
274              
275             Brad Haywood
276              
277             =head1 LICENSE
278              
279             You may distribute this code under the same terms as Perl itself.
280              
281             =cut
282             1;