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.015';
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   47378 use strict;
  1         3  
  1         28  
27 1     1   9 use warnings;
  1         2  
  1         29  
28 1     1   5 use List::Util qw( any max );
  1         3  
  1         106  
29 1     1   9 use Path::Tiny qw( path );
  1         4  
  1         45  
30 1     1   15 use Module::Runtime qw( use_module );
  1         3  
  1         8  
31 1     1   722 use Beam::Wire;
  1         390444  
  1         47  
32 1     1   533 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         83  
34 1     1   482 use Pod::Simple::SimpleTree;
  1         29683  
  1         39  
35 1     1   8 use Term::ANSIColor qw( color );
  1         2  
  1         997  
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 26070 my ( $class, $container ) = @_;
53              
54 5 100       17 if ( !$container ) {
55 2         20 return $class->_list_containers;
56             }
57              
58 3 100       11 if ( !$class->_list_services( $container ) ) {
59 1         35 warn qq{No runnable services in container "$container"\n};
60 1         9 return 1;
61             }
62              
63 2         13 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   7 my ( $class ) = @_;
76             die "Cannot list containers: BEAM_PATH environment variable not set\n"
77 2 100       12 unless $ENV{BEAM_PATH};
78              
79 1         5 my %containers = find_containers();
80 1         8 my @container_names = sort keys %containers;
81 1         3 my $printed = 0;
82 1         4 for my $i ( 0..$#container_names ) {
83 3 100       12 if ( $printed ) {
84 1         12 print "\n";
85 1         3 $printed = 0;
86             }
87 3         26 $printed += $class->_list_services( $containers{ $container_names[ $i ] } );
88             }
89              
90 1         13 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   15 my ( $class, $container ) = @_;
103 6         23 my $path = find_container_path( $container );
104 6         87 my $cname = $path->basename( @EXTS );
105 6         357 my $wire = Beam::Wire->new(
106             file => $path,
107             );
108              
109 6         113281 my $config = $wire->config;
110 6         45 my %services;
111 6         23 for my $name ( keys %$config ) {
112 37         103 my ( $name, $abstract ) = _list_service( $wire, $name, $config->{$name} );
113 37 100       495 next unless $name;
114 17         48 $services{ $name } = $abstract;
115             }
116 6 100       35 return 0 unless keys %services;
117              
118 4         16 my ( $bold, $reset ) = ( color( 'bold' ), color( 'reset' ) );
119 4   100     162 print "$bold$cname$reset" . ( eval { " -- " . $wire->get( '$summary' ) } || '' ) . "\n";
120              
121 4         4006 my $size = max map { length } keys %services;
  17         48  
122 4         26 print join( "\n", map { sprintf "- $bold%-${size}s$reset -- %s", $_, $services{ $_ } } sort keys %services ), "\n";
  17         157  
123 4         74 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   84 my ( $wire, $name, $svc ) = @_;
139              
140             # If it doesn't look like a service, we don't care
141 40 100       106 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         2397 my %svc = %{ $wire->normalize_config( $svc ) };
  30         84  
146 30 100       1355 if ( $svc{ ref } ) {
147 3         14 my $ref_svc = $wire->get_config( $svc{ ref } );
148 3         120 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         85 my %merged = $wire->merge_config( %svc );
154             #; use Data::Dumper;
155             #; print "$name merged: " . Dumper \%merged;
156 27         629 my $class = $merged{ class };
157 27 50       41 my @roles = @{ $merged{ with } || [] };
  27         118  
158              
159             # Can we determine this object is runnable without loading anything?
160 27 50       75 if ( grep { $_ eq 'Beam::Runnable' } @roles ) {
  0         0  
161 0         0 return _get_service_info( $name, $class, \%merged );
162             }
163              
164 27 100   27   46 if ( eval { any {; use_module( $_ )->DOES( 'Beam::Runnable' ) } $class, @roles } ) {
  27         184  
  27         87  
165 17         4933 return _get_service_info( $name, $class, \%merged );
166             }
167              
168 10         1263 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   43 my ( $name, $class, $config ) = @_;
184 17 100       47 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         87 return $name, $config->{args}{summary};
192             }
193 5         1942 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         22382 my @nodes = @{$pod_root}[2..$#$pod_root];
  4         17  
200             #; print Dumper \@nodes;
201 4 100       12 my ( $name_i ) = grep { $nodes[$_][0] eq 'head1' && $nodes[$_][2] eq 'NAME' } 0..$#nodes;
  65         173  
202 4 100       23 return $name, $class unless defined $name_i;
203              
204 3         14 my $abstract = $nodes[ $name_i + 1 ][2];
205 3         70 return $name, $abstract;
206             }
207              
208             1;
209              
210             __END__