File Coverage

lib/Bread/Runner.pm
Criterion Covered Total %
statement 91 99 91.9
branch 24 30 80.0
condition 10 14 71.4
subroutine 16 19 84.2
pod 2 2 100.0
total 143 164 87.2


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