File Coverage

blib/lib/Beam/Runner/Command/list.pm
Criterion Covered Total %
statement 96 98 97.9
branch 28 30 93.3
condition 2 2 100.0
subroutine 16 16 100.0
pod 1 1 100.0
total 143 147 97.2


line stmt bran cond sub pod time code
1             package Beam::Runner::Command::list;
2             our $VERSION = '0.016';
3             # ABSTRACT: List the available containers and services
4              
5             #pod =head1 SYNOPSIS
6             #pod
7             #pod beam list
8             #pod beam list
9             #pod
10             #pod =head1 DESCRIPTION
11             #pod
12             #pod List the available containers found in the directories defined in
13             #pod C, and list the runnable services found in them. Also show
14             #pod the C<$summary> from the container file, and the abstract from every
15             #pod service.
16             #pod
17             #pod When listing services, this command must load every single class
18             #pod referenced in the container, but it will not instanciate any object.
19             #pod
20             #pod =head1 SEE ALSO
21             #pod
22             #pod L, L, L
23             #pod
24             #pod =cut
25              
26 1     1   46803 use strict;
  1         2  
  1         31  
27 1     1   5 use warnings;
  1         2  
  1         35  
28 1     1   5 use List::Util qw( any max );
  1         2  
  1         102  
29 1     1   7 use Path::Tiny qw( path );
  1         3  
  1         46  
30 1     1   6 use Module::Runtime qw( use_module );
  1         2  
  1         17  
31 1     1   629 use Beam::Wire;
  1         390371  
  1         47  
32 1     1   521 use Beam::Runner::Util qw( find_container_path find_containers );
  1         3  
  1         64  
33 1     1   8 use Pod::Find qw( pod_where );
  1         2  
  1         67  
34 1     1   459 use Pod::Simple::SimpleTree;
  1         30606  
  1         41  
35 1     1   9 use Term::ANSIColor qw( color );
  1         2  
  1         1025  
36              
37             # The extensions to remove to show the container's name
38             my @EXTS = grep { $_ } @Beam::Runner::Util::EXTS;
39              
40             #pod =method run
41             #pod
42             #pod my $exit = $class->run;
43             #pod my $exit = $class->run( $container );
44             #pod
45             #pod Print the list of containers to C, or, if C<$container> is given,
46             #pod print the list of runnable services. A runnable service is an object
47             #pod that consumes the L role.
48             #pod
49             #pod =cut
50              
51             sub run {
52 5     5 1 26463 my ( $class, $container ) = @_;
53              
54 5 100       50 if ( !$container ) {
55 2         9 return $class->_list_containers;
56             }
57              
58 3 100       17 if ( !$class->_list_services( $container ) ) {
59 1         46 warn qq{No runnable services in container "$container"\n};
60 1         8 return 1;
61             }
62              
63 2         14 return 0;
64             }
65              
66             #=sub _list_containers
67             #
68             # my $exit = $class->_list_containers
69             #
70             # Print all the containers found in the BEAM_PATH to STDOUT
71             #
72             #=cut
73              
74             sub _list_containers {
75 2     2   5 my ( $class ) = @_;
76             die "Cannot list containers: BEAM_PATH environment variable not set\n"
77 2 100       14 unless $ENV{BEAM_PATH};
78              
79 1         16 my %containers = find_containers();
80 1         10 my @container_names = sort keys %containers;
81 1         3 my $printed = 0;
82 1         5 for my $i ( 0..$#container_names ) {
83 3 100       9 if ( $printed ) {
84 1         12 print "\n";
85 1         4 $printed = 0;
86             }
87 3         26 $printed += $class->_list_services( $containers{ $container_names[ $i ] } );
88             }
89              
90 1         15 return 0;
91             }
92              
93             #=sub _list_services
94             #
95             # my $exit = $class->_list_services( $container );
96             #
97             # Print all the runnable services found in the container to STDOUT
98             #
99             #=cut
100              
101             sub _list_services {
102 6     6   18 my ( $class, $container ) = @_;
103 6         26 my $path = find_container_path( $container );
104 6         95 my $cname = $path->basename( @EXTS );
105 6         408 my $wire = Beam::Wire->new(
106             file => $path,
107             );
108              
109 6         115398 my $config = $wire->config;
110 6         47 my %services;
111 6         26 for my $name ( keys %$config ) {
112 37         98 my ( $name, $abstract ) = _list_service( $wire, $name, $config->{$name} );
113 37 100       492 next unless $name;
114 17         51 $services{ $name } = $abstract;
115             }
116 6 100       41 return 0 unless keys %services;
117              
118 4         24 my ( $bold, $reset ) = ( color( 'bold' ), color( 'reset' ) );
119 4   100     192 print "$bold$cname$reset" . ( eval { " -- " . $wire->get( '$summary' ) } || '' ) . "\n";
120              
121 4         4149 my $size = max map { length } keys %services;
  17         50  
122 4         26 print join( "\n", map { sprintf "- $bold%-${size}s$reset -- %s", $_, $services{ $_ } } sort keys %services ), "\n";
  17         149  
123 4         80 return 1;
124             }
125              
126             #=sub _list_service
127             #
128             # my $service_info = _list_service( $wire, $name, $config );
129             #
130             # If the given service is a runnable service, return the information
131             # about it ready to be printed to STDOUT. $wire is a Beam::Wire object,
132             # $name is the name of the service, $config is the service's
133             # configuration hash
134             #
135             #=cut
136              
137             sub _list_service {
138 40     40   88 my ( $wire, $name, $svc ) = @_;
139              
140             # If it doesn't look like a service, we don't care
141 40 100       113 return unless $wire->is_meta( $svc, 1 );
142              
143             # Services that are just references to other services should still
144             # be available under their referenced name
145 30         2340 my %svc = %{ $wire->normalize_config( $svc ) };
  30         88  
146 30 100       1381 if ( $svc{ ref } ) {
147 3         11 my $ref_svc = $wire->get_config( $svc{ ref } );
148 3         128 return _list_service( $wire, $name, $ref_svc );
149             }
150              
151             # Services that extend other services must be resolved to find their
152             # class and roles
153 27         90 my %merged = $wire->merge_config( %svc );
154             #; use Data::Dumper;
155             #; print "$name merged: " . Dumper \%merged;
156 27         587 my $class = $merged{ class };
157 27 50       44 my @roles = @{ $merged{ with } || [] };
  27         117  
158              
159             # Can we determine this object is runnable without loading anything?
160 27 50       78 if ( grep { $_ eq 'Beam::Runnable' } @roles ) {
  0         0  
161 0         0 return _get_service_info( $name, $class, \%merged );
162             }
163              
164 27 100   27   47 if ( eval { any {; use_module( $_ )->DOES( 'Beam::Runnable' ) } $class, @roles } ) {
  27         153  
  27         80  
165 17         5096 return _get_service_info( $name, $class, \%merged );
166             }
167              
168 10         1155 return;
169             }
170              
171             #=sub _get_service_info( $name, $class )
172             #
173             # my ( $name, $abstract ) = _get_service_info( $name, $class, $config );
174             #
175             # Get the information about the given service. Opens the C<$class>
176             # documentation to find the class's abstract (the C<=head1 NAME>
177             # section). If C<$config> contains a C in its C hashref,
178             # will use that in place of the POD documentation.
179             #
180             #=cut
181              
182             sub _get_service_info {
183 17     17   44 my ( $name, $class, $config ) = @_;
184 17 100       49 if ( $config->{args}{summary} ) {
185             # XXX: This does not allow good defaults from the object
186             # itself... There's no way to get that without instantiating the
187             # object, which means potentially doing a lot of work like
188             # connecting to a database. If we had some way of making things
189             # extra lazy, we could create the object without doing much
190             # work...
191 12         88 return $name, $config->{args}{summary};
192             }
193 5         1938 my $pod_path = pod_where( { -inc => 1 }, $class );
194 5 100       40 return $name, $class unless $pod_path;
195              
196 4         53 my $pod_root = Pod::Simple::SimpleTree->new->parse_file( $pod_path )->root;
197             #; use Data::Dumper;
198             #; print Dumper $pod_root;
199 4         22868 my @nodes = @{$pod_root}[2..$#$pod_root];
  4         16  
200             #; print Dumper \@nodes;
201 4 100       16 my ( $name_i ) = grep { $nodes[$_][0] eq 'head1' && $nodes[$_][2] eq 'NAME' } 0..$#nodes;
  65         173  
202 4 100       22 return $name, $class unless defined $name_i;
203              
204 3         10 my $abstract = $nodes[ $name_i + 1 ][2];
205 3         75 return $name, $abstract;
206             }
207              
208             1;
209              
210             __END__