File Coverage

lib/App/SimulateReads/CLI/App.pm
Criterion Covered Total %
statement 12 116 10.3
branch 0 36 0.0
condition 0 9 0.0
subroutine 4 30 13.3
pod 8 8 100.0
total 24 199 12.0


line stmt bran cond sub pod time code
1             package App::SimulateReads::CLI::App;
2             # ABSTRACT: App::SimulateReads::CLI subclass for command line application interface.
3              
4 1     1   1996 use App::SimulateReads::Base 'class';
  1         2  
  1         5  
5 1     1   6 use Path::Class 'file';
  1         2  
  1         49  
6 1     1   385 use Pod::Usage;
  1         27347  
  1         111  
7 1     1   8 use Try::Tiny;
  1         3  
  1         1444  
8              
9             extends 'App::SimulateReads::CLI';
10              
11             with 'App::SimulateReads::Role::ParseArgv';
12              
13             our $VERSION = '0.06'; # VERSION
14              
15             has 'command_stack' => (
16             traits => ['Array'],
17             is => 'ro',
18             isa => 'ArrayRef[HashRef]',
19             default => sub { [] },
20             handles => {
21             add_command => 'push',
22             get_command => 'get',
23             map_command => 'map',
24             has_no_command => 'is_empty'
25             }
26             );
27              
28             has 'app_path' => (
29             is => 'ro',
30             isa => 'Str',
31             builder => '_build_app_path'
32             );
33              
34             sub _build_app_path {
35             # Determine dynamic the app path that inherit from this class
36             # If no one is inheriting, return this class path
37 0     0     my $class = (caller(1))[3];
38 0           $class =~ s/::new//;
39 0           my $command_pm = file(split /::/ => "$class.pm");
40 0           return $INC{$command_pm};
41             }
42              
43             sub command_map_bultin {
44 0     0 1   help => \&help_command,
45             man => \&man_command
46             }
47              
48       0 1   sub command_map {
49             # It needs to be override
50             }
51              
52             sub error {
53 0     0 1   my ($self, $error_msg) = @_;
54 0           my $sender = $self->_whois;
55 0           chomp $error_msg;
56 0           die "$sender: $error_msg\n";
57             }
58              
59             sub _whois {
60 0     0     my $self = shift;
61 0           my $sender = $self->progname;
62 0     0     my @commands = $self->map_command(sub { $_->{name} });
  0            
63 0 0         $sender .= " @commands" unless $self->has_no_command;
64 0           return $sender;
65             }
66              
67             sub _try_msg {
68 0     0     my $self = shift;
69 0           return sprintf "Try '%s --help' for more information" => $self->_whois;
70             }
71              
72             sub _help_text {
73 0     0     my ($self, $path) = @_;
74 0   0       $path ||= $self->app_path;
75 0           pod2usage(-input => $path, -verbose => 99, -sections => ['SYNOPSIS'], -exitval => 0);
76             }
77              
78             sub _man_text {
79 0     0     my ($self, $path) = @_;
80 0   0       $path ||= $self->app_path;
81 0           pod2usage(-input => $path, -verbose => 2, -exitval => 0);
82             }
83              
84             sub help_command {
85 0     0 1   my ($self, $argv) = @_;
86 0           my %command_map = $self->command_map;
87 0           $self->_dispatcher(\%command_map, $argv);
88 0 0         $self->error("Too many arguments: '@$argv'\n" . $self->_try_msg) if @$argv;
89 0           my $path;
90 0 0         $path = $self->get_command(-1)->{path} unless $self->has_no_command;
91 0           return $self->_help_text($path);
92             }
93              
94             sub man_command {
95 0     0 1   my ($self, $argv) = @_;
96 0           my %command_map = $self->command_map;
97 0           $self->_dispatcher(\%command_map, $argv);
98 0 0         $self->error("Too many arguments: '@$argv'\n" . $self->_try_msg) if @$argv;
99 0           my $path;
100 0 0         $path = $self->get_command(-1)->{path} unless $self->has_no_command;
101 0           return $self->_man_text($path);
102             }
103              
104             sub run_no_command {
105 0     0 1   my ($self, $argv) = @_;
106 0           my ($opts, $args);
107              
108             try {
109 0     0     ($opts, $args) = $self->parser($argv, $self->opt_spec);
110             } catch {
111 0     0     $self->error("$_" . $self->_try_msg);
112 0           };
113              
114 0 0         $self->_help_text if $opts->{help};
115 0 0         $self->_man_text if $opts->{man};
116             }
117              
118             sub run_command {
119 0     0 1   my ($self, $argv) = @_;
120 0           my %command_map = $self->command_map;
121 0           $self->_dispatcher(\%command_map, $argv);
122              
123 0           my $command = $self->get_command(-1);
124 0           my $o = $command->{class}->new;
125              
126             # $args has at least $argv if no opt has been passed
127 0           my ($opts, $args) = (undef, $argv);
128              
129 0 0         if ($o->can('opt_spec')) {
130             try {
131 0     0     ($opts, $args) = $self->parser($argv, $o->opt_spec);
132             } catch {
133 0     0     $self->error("$_" . $self->_try_msg);
134 0           };
135             }
136              
137 0 0         $self->_help_text($command->{path}) if $opts->{help};
138 0 0         $self->_man_text($command->{path}) if $opts->{man};
139              
140             # Deep copy the arguments, just in case the user
141             # manages to mess with
142 0           my %opts_copy = %$opts;
143 0           my @args_copy = @$args;
144              
145 0 0         if ($o->can('validate_args')) {
146             try {
147 0     0     $o->validate_args(\@args_copy);
148             } catch {
149 0     0     $self->error("$_" . $self->_try_msg);
150 0           };
151             }
152              
153 0 0         if ($o->can('validate_opts')) {
154             try {
155 0     0     $o->validate_opts(\%opts_copy);
156             } catch {
157 0     0     $self->error("$_" . $self->_try_msg);
158 0           };
159             }
160              
161             try {
162 0     0     $o->execute($opts, $args);
163             } catch {
164 0     0     $self->error($_);
165 0           };
166             }
167              
168             sub _command_loading {
169 0     0     my ($self, $command_class) = @_;
170 0           my $command_pm = file(split /::/ => "$command_class.pm");
171              
172 0           eval { require $command_pm };
  0            
173 0 0         die $@ if $@;
174              
175 0 0         my $command_class_path = $INC{ $command_pm }
176             or die "$command_class not found in \%INC";
177              
178 0           return $command_class_path;
179             }
180              
181             sub _dispatcher {
182 0     0     my ($self, $command_map, $argv) = @_;
183              
184 0 0 0       if (@$argv && exists $command_map->{$argv->[0]}) {
185 0           my $command_name = shift @$argv;
186 0           my $command_class = $command_map->{$command_name};
187 0           my $command_class_path = $self->_command_loading($command_class);
188              
189 0           $self->add_command({
190             'name' => $command_name,
191             'class' => $command_class,
192             'path' => $command_class_path
193             });
194              
195 0 0         unless ($command_class->can('execute')) {
196 0           die "Not defined method 'execute' for $command_class";
197             }
198              
199 0 0         if ($command_class->can('subcommand_map')) {
200 0           my %command_map = $command_class->subcommand_map;
201 0           return $self->_dispatcher(\%command_map, $argv);
202             }
203             }
204             }
205              
206             sub run {
207 0     0 1   my $self = shift;
208 0           my @argv = @{ $self->argv };
  0            
209 0           my %command_map = $self->command_map;
210 0           my %command_map_bultin = $self->command_map_bultin;
211              
212 0 0         $self->_help_text unless scalar @argv;
213              
214 0           given ($argv[0]) {
215 0           when (%command_map_bultin) {
216 0           my $command_name = shift @argv;
217 0           my $command_method = $command_map_bultin{$command_name};
218 0           $self->$command_method(\@argv);
219             }
220 0           when (%command_map) {
221 0           $self->run_command(\@argv);
222             }
223 0           when (/^-/) {
224 0           $self->run_no_command(\@argv);
225             }
226 0           default {
227 0           $self->error("Unknown command '$argv[0]'\n" . $self->_try_msg);
228             }
229             }
230             }
231              
232             __END__
233              
234             =pod
235              
236             =encoding UTF-8
237              
238             =head1 NAME
239              
240             App::SimulateReads::CLI::App - App::SimulateReads::CLI subclass for command line application interface.
241              
242             =head1 VERSION
243              
244             version 0.06
245              
246             =head1 SYNOPSIS
247              
248             extends 'App::SimulateReads::CLI::App';
249              
250             =head1 DESCRIPTION
251              
252             This is the base interface to application class.
253             Classes need to override command_map method to
254             provide command arguments
255              
256             =head1 METHODS
257              
258             =head2 command_stack
259              
260             This method returns a stach with commands and subcommands
261              
262             =head2 app_path
263              
264             This method returns the application class path
265              
266             =head2 command_map_bultin
267              
268             This method retuns a hash with bultin command
269              
270             =head2 command_map
271              
272             This method needs to be override by child to provide
273             command arguments. It is expected to return a hash
274              
275             =head2 error
276              
277             This method prints a formatted error message
278              
279             =head2 help_command
280              
281             This method calls help message to the command up
282             in the C<command_stack>. If no command was passed,
283             it calls help message to the app itself. Help messages
284             are in pod format inside the app, command classes
285              
286             =head2 man_command
287              
288             This method calls man message to the command up
289             in the C<command_stack>. If no command was passed,
290             it calls man message to the app itself. Man messages
291             are in pod format inside the app, command classes
292              
293             =head2 run_no_command
294              
295             This method runs app options, those defined in
296             C<opt_spec> method
297              
298             =head2 run_command
299              
300             This method checkes C<command_stack> and executes the
301             command up mathods C<validate_args>, C<validate_opts>
302             and C<execute>
303              
304             =head2 run
305              
306             This method checks the arguments passed to the
307             application and call the appropriate methods
308             C<run_no_command>, C<run_command> or
309             C<help_command>/C<man_command>
310              
311             =head1 SEE ALSO
312              
313             =over 4
314              
315             =item *
316              
317             L<App::SimulateReads::CLI::Command>
318              
319             =back
320              
321             =head1 AUTHOR
322              
323             Thiago L. A. Miller <tmiller@mochsl.org.br>
324              
325             =head1 COPYRIGHT AND LICENSE
326              
327             This software is Copyright (c) 2017 by Teaching and Research Institute from Sírio-Libanês Hospital.
328              
329             This is free software, licensed under:
330              
331             The GNU General Public License, Version 3, June 2007
332              
333             =cut