File Coverage

blib/lib/Bread/Runner.pm
Criterion Covered Total %
statement 94 102 92.1
branch 24 30 80.0
condition 10 14 71.4
subroutine 17 20 85.0
pod 2 2 100.0
total 147 168 87.5


line stmt bran cond sub pod time code
1             package Bread::Runner;
2 4     4   541571 use 5.020;
  4         55  
3 4     4   42 use strict;
  4         7  
  4         77  
4 4     4   18 use warnings;
  4         6  
  4         194  
5              
6             # ABSTRACT: run ALL the apps via Bread::Board
7              
8             our $VERSION = '0.904'; # VERSION
9              
10 4     4   2269 use Bread::Board qw();
  4         9186276  
  4         189  
11 4     4   47 use Carp;
  4         9  
  4         349  
12 4     4   37 use Module::Runtime qw(use_module);
  4         10  
  4         65  
13 4     4   339 use Scalar::Util qw(blessed);
  4         9  
  4         178  
14 4     4   4017 use Getopt::Long;
  4         45028  
  4         22  
15 4     4   2158 use Log::Any qw($log);
  4         26288  
  4         31  
16 4     4   6918 use Try::Tiny;
  4         9  
  4         4229  
17              
18              
19             sub run {
20 6     6 1 21538 my ( $class, $bb_class, $opts ) = @_;
21              
22 6         25 my ($bb, $service) = $class->setup($bb_class, $opts);
23              
24 3 50       15 $class->_hook( 'pre_run', $bb, $service, $opts ) if $opts->{pre_run};
25              
26 3   100     20 my $run_methods = $opts->{run_method} || ['run'];
27 3 100       22 $run_methods = [$run_methods] unless ref($run_methods) eq 'ARRAY';
28 3         6 my $method;
29 3         8 foreach my $m (@$run_methods) {
30 3 100       20 next unless $service->can($m);
31 2         5 $method = $m;
32 2         7 last;
33             }
34 3 100       8 unless ($method) {
35 1         7 my $msg = ref($service)." does not provide any run_method: "
36             . join( ', ', @$run_methods );
37 1         5 $log->error($msg);
38 1         59 croak $msg;
39             }
40              
41             my $rv = try {
42 2 50   2   204 $log->infof("Running %s->%s",ref($service), $method) unless $opts->{no_startup_logmessage};
43 2         186 return $service->$method;
44             }
45             catch {
46 1     1   27 my $e = $_;
47 1         4 my $msg;
48 1 50 33     7 if ( blessed($e) && $e->can('message') ) {
49 0         0 $msg = $e->message;
50             }
51             else {
52 1         2 $msg = $e;
53             }
54 1         8 $log->errorf( "%s died with %s", $method, $msg );
55 1         108 croak $msg;
56 2         22 };
57              
58 1 50       50 $class->_hook( 'post_run', $bb, $service, $opts ) if $opts->{post_run};
59 1         4 return $rv;
60             }
61              
62              
63             sub setup {
64 9     9 1 1539 my ( $class, $bb_class, $opts ) = @_;
65 9   50     28 $opts ||= {};
66              
67 9   66     37 my $service_name = $opts->{service} || $0;
68 9         64 $service_name =~ s{^(?:.*\bbin/)(.+)$}{$1};
69 9         26 $service_name =~ s{/}{_}g;
70              
71 9         45 my $bb = $class->_compose_breadboard( $bb_class, $opts );
72              
73 8   100     67 my $bb_container = $opts->{container} || 'App';
74             my $service_bb = try {
75 8     8   734 $bb->fetch( $bb_container . '/' . $service_name );
76             }
77             catch {
78 2     2   3900 $log->error($_);
79 2         115 croak $_;
80 8         73 };
81              
82 6         4305 my $service_class = $service_bb->class;
83 6         81 use_module($service_class);
84              
85 6         96390 my $service;
86 6 100       295 if ( $service_bb->has_parameters ) {
87 2         170 my $params = $service_bb->parameters;
88 2         29 my @spec;
89 2         17 while ( my ( $name, $def ) = each %$params ) {
90 10         20 my $spec = "$name";
91 10 50       27 if ( my $isa = $def->{isa} ) {
92 10 100       50 if ( $isa eq 'Int' ) { $spec .= "=i" }
  2 100       7  
    100          
    100          
    50          
93 2         6 elsif ( $isa eq 'Str' ) { $spec .= "=s" }
94 2         6 elsif ( $isa eq 'Bool' ) { $spec .= '!' }
95 2         6 elsif ( $isa eq 'ArrayRef' ) { $spec .= '=s@' }
96 2         6 elsif ( $isa eq 'HashRef' ) { $spec .= '=s%' }
97             }
98              
99             # TODO required
100             # TODO default
101             # TODO maybe we can use MooseX::Getopt?
102 10         38 push( @spec, $spec );
103             }
104 2         6 my %commandline;
105              
106 2         16 GetOptions( \%commandline, @spec );
107 2         2120 $service = $service_bb->get( \%commandline );
108             }
109             else {
110 4         226 $service = $service_bb->get;
111             }
112              
113 6         24264 return ($bb, $service);
114             }
115              
116             sub _compose_breadboard {
117 9     9   25 my ( $class, $bb_class, $opts ) = @_;
118              
119 9         54 use_module($bb_class);
120 9   100     180020 my $init_method = $opts->{init_method} || 'init';
121 9 100       97 if ( $bb_class->can($init_method) ) {
122 8         35 return $bb_class->$init_method($opts);
123             }
124             else {
125 1         9 my $msg =
126             "$bb_class does not implement a method $init_method (to compose the Bread::Board)";
127 1         6 $log->error($msg);
128 1         54 croak $msg;
129             }
130             }
131              
132             sub _hook {
133 0     0     my ( $class, $hook_name, $bb, $service, $opts ) = @_;
134              
135 0           my $hook = $opts->{$hook_name};
136             try {
137 0     0     $log->infof( "Running hook %s", $hook_name );
138 0           $hook->( $service, $bb, $opts );
139             }
140             catch {
141 0     0     $log->errorf( "Could not run hook %s: %s", $hook_name, $_ );
142 0           croak $_;
143             }
144 0           }
145              
146             1;
147              
148             __END__
149              
150             =pod
151              
152             =encoding UTF-8
153              
154             =head1 NAME
155              
156             Bread::Runner - run ALL the apps via Bread::Board
157              
158             =head1 VERSION
159              
160             version 0.904
161              
162             =head1 SYNOPSIS
163              
164             # Define the components of your app in a Bread::Board
165             container 'YourProduct' => as {
166             container 'App' => as {
167             service 'api.psgi' => (
168             # ...
169             );
170             service 'some_script' => (
171             # ...
172             )
173             };
174             };
175            
176             # Write one generic wrapper script to run all your services
177             # bin/generic_runner.pl
178             use Bread::Runner;
179             Bread::Runner->run('YourProduct');
180            
181             # Symlink this generic runner to filenames matchin your services
182             ln -s bin/generic_runner.pl bin/api.psgi
183             ln -s bin/generic_runner.pl bin/some_script
184            
185             # Never write a wrapper script again!
186              
187             =head1 DESCRIPTION
188              
189             C<Bread::Runner> provides an easy way to re-use your L<Bread::Board>
190             to run all your scripts via a simple and unified method.
191              
192             This of course only makes sense for big-ish apps which consist of more
193             than just one script. But in my experience this is true for all apps,
194             as you will need countless helper scripts, importer, exporter,
195             cron-jobs, fixups etc.
196              
197             If you still keep the code of your scripts in your scripts, I strongly
198             encourage you to join us in the 21st century and move all your code
199             into proper classes and replace your scripts by thin wrappers that
200             call those classes. And if you use C<Bread::Runner>, you'll only need
201             one wrapper (though you can have as many as you like, as TIMTOWTDI)
202              
203             =head2 Real-Live Example
204              
205             TODO
206              
207             =head2 Guessing the service name from $0
208              
209             TODO
210              
211             =head1 METHODS
212              
213             =head2 run
214              
215             Bread::Runner->run('YourProduct', \%opts);
216              
217             Bread::Runner->run('YourProduct', {
218             service => 'some_script.pl'
219             });
220              
221             Initialize your Bread::Board, find the correct service, initialize the
222             service, and then run it!
223              
224             =head2 setup
225              
226             my ($bread_board, $service) = Bread::Runner->_setup( 'YourProduct', \%opts );
227              
228             Initialize and compose your C<Bread::Board> and find and initialize the correct C<service>.
229              
230             Usually you will just call L<run>, but maybe you want to do something fancy..
231              
232             =head1 OPTIONS
233              
234             L<setup> and L<run> take the following options as a hashref
235              
236             =head3 service
237              
238             Default: C<$0> modulo some cleanup magic, see L<Guessing the service name from $0>
239              
240             The name of the service to use.
241              
242             If you do not want to use this magic, pass in the explizit service
243             name you want to use. This could be hardcoded, or you could come up
244             with an alternative implementation to get the service name from the
245             environment available to a generic wrapper script.
246              
247             =head3 container
248              
249             Default: "App"
250              
251             The name of the C<Bread::Board> container containing your services.
252              
253             =head3 init_method
254              
255             Default: "init"
256              
257             The name of the method in the class implementing your C<Bread::Board>
258             that will return the topmost container.
259              
260             =head3 run_method
261              
262             Default: ["run"]
263              
264             An arrayref of names of potential methods call in your services to
265             make them do their job.
266              
267             Useful for running legacy classes via C<Bread::Runner>.
268              
269             =head3 pre_run
270              
271             A subref to be called just before C<run> is called.
272              
273             Gets the following things as a list in this order
274              
275             =over
276              
277             =item * the C<Bread::Board> container
278              
279             =item * the initated service
280              
281             =item * the opts hashref (so you can pass on more stuff from your wrapper)
282              
283             =back
284              
285             You could use this hook to do some further initalistion, setup etc
286             that might not be doable in C<Bread::Board> itself.
287              
288             =head3 post_run
289              
290             A subref to be called just after C<run> is called.
291              
292             Gets the same stuff like C<pre_run>.
293              
294             Could be used for cleanup etc.
295              
296             =head3 no_startup_logmessage
297              
298             Set this to a true value to prevent the startup log message.
299              
300             =head1 THANKS
301              
302             Thanks to
303              
304             =over
305              
306             =item *
307              
308             L<validad.com|http://www.validad.com/> for supporting Open Source.
309              
310             =item *
311              
312             L<Klaus Ita|https://metacpan.org/author/KOKI> for feedback & input during inital in-house development
313              
314             =back
315              
316             =head1 AUTHOR
317              
318             Thomas Klausner <domm@plix.at>
319              
320             =head1 COPYRIGHT AND LICENSE
321              
322             This software is copyright (c) 2016 - 2021 by Thomas Klausner.
323              
324             This is free software; you can redistribute it and/or modify it under
325             the same terms as the Perl 5 programming language system itself.
326              
327             =cut