File Coverage

blib/lib/App/CmdDispatch.pm
Criterion Covered Total %
statement 112 122 91.8
branch 37 52 71.1
condition 8 13 61.5
subroutine 22 22 100.0
pod 9 9 100.0
total 188 218 86.2


line stmt bran cond sub pod time code
1             package App::CmdDispatch;
2              
3 11     11   433243 use warnings;
  11         30  
  11         348  
4 11     11   54 use strict;
  11         19  
  11         320  
5              
6 11     11   10779 use Config::Tiny;
  11         17876  
  11         367  
7 11     11   11582 use Term::ReadLine;
  11         46788  
  11         373  
8 11     11   6992 use App::CmdDispatch::IO;
  11         26  
  11         368  
9 11     11   6280 use App::CmdDispatch::Table;
  11         27  
  11         17357  
10              
11             our $VERSION = '0.42';
12              
13             sub new
14             {
15 45     45 1 31755 my ( $class, $commands, $options ) = @_;
16              
17 45   100     221 $options ||= {};
18 45 100       400 die "Command definition is not a hashref.\n" unless ref $commands eq ref {};
19 43 100       110 die "No commands specified.\n" unless keys %{$commands};
  43         191  
20 42 100 66     324 die "Options parameter is not a hashref.\n" unless $options and ref $options eq ref {};
21              
22 41         72 $options = { %{$options} };
  41         168  
23 41         113 my $config_file = delete $options->{config};
24 41         60 my $aliases;
25 41         150 my $self = bless { config => $options }, $class;
26 41 50 33     267 $self->{_command_sorter} = delete $self->{config}->{command_sort}
27             if $self->{config}->{command_sort} && 'CODE' eq ref $self->{config}->{command_sort};
28 41 100       108 if( defined $config_file )
29             {
30 7 100       168 die "Supplied config is not a file.\n" unless -f $config_file;
31 6         19 $self->_initialize_config( $config_file );
32             }
33 40         89 $aliases = delete $self->{config}->{alias};
34 40 100       155 $aliases = {} unless ref $aliases eq ref {};
35              
36 40         143 $commands = $self->_setup_commands( $commands );
37              
38             # TODO - replace the hard-coded Table module name with a parameter.
39 40         239 my $table = App::CmdDispatch::Table->new( $commands, $aliases );
40 37 100       154 if( $self->{_helper} )
41             {
42 22         81 $self->{_helper}->normalize_command_help( $table );
43 22   33     373 $self->{_command_sorter} ||= ( ref $self->{_helper} )->can( "sort_commands" );
44             }
45 37         80 $self->{table} = $table;
46 37         121 $self->_initialize_io_object();
47 37   100 13   214 $self->{_command_sorter} ||= sub { return ( sort @_ ); };
  13         64  
48              
49 37         220 return $self;
50             }
51              
52 1     1 1 9 sub get_config { return $_[0]->{config}; }
53              
54             sub run
55             {
56 49     49 1 1638 my ( $self, $cmd, @args ) = @_;
57              
58             eval {
59 49         251 $self->{table}->run( $self, $cmd, @args );
60 46         146 1;
61             } or do
62 49 100       65 {
63 3         4 my $ex = $@;
64 3 50       13 if( ref( $ex ) =~ /\AApp::CmdDispatch::Exception/ )
65             {
66 3         9 $self->_print( $ex->why(), "\n" );
67 3         22 $self->command_hint;
68             }
69             else
70             {
71 0         0 die $ex;
72             }
73             };
74 49         172 return;
75             }
76              
77             sub command_list
78             {
79 44     44 1 2776 my ( $self ) = @_;
80 44         159 return $self->{_command_sorter}->( $self->{table}->command_list() );
81             }
82              
83             sub command_hint
84             {
85 3     3 1 3 my ( $self ) = @_;
86 3 50       15 return $self->{_helper}->hint() if defined $self->{_helper};
87 0         0 $self->_print( "Commands: ", join( ', ', $self->command_list() ), "\n" );
88 0         0 return;
89             }
90              
91             sub hint
92             {
93 19     19 1 13683 my ( $self, $arg ) = @_;
94             eval {
95 19         55 $self->run( 'hint', $arg );
96 19         48 1;
97             } or do
98 19 50       35 {
99 0 0       0 return $self->{_helper}->hint( $arg ) if defined $self->{_helper};
100 0         0 $self->_print( "Commands: ", join( ', ', $self->command_list() ), "\n" );
101             };
102 19         86 return;
103             }
104              
105             sub help
106             {
107 19     19 1 8201 my ( $self, $arg ) = @_;
108             eval {
109 19         38 $self->run( 'help', $arg );
110 19         42 1;
111             } or do
112 19 50       24 {
113 0 0       0 return $self->{_helper}->help( $arg ) if defined $self->{_helper};
114 0         0 $self->_print( "Commands: ", join( ', ', $self->command_list() ), "\n" );
115             };
116 19         35 return;
117             }
118              
119 12     12 1 1164 sub alias_list { return $_[0]->{table}->alias_list(); }
120              
121             sub shell
122             {
123 3     3 1 15 my ( $self ) = @_;
124              
125 3         8 $self->_print( "Enter a command or 'quit' to exit:\n" );
126 3         25 while ( my $line = $self->_prompt( '> ' ) )
127             {
128 5         54 chomp $line;
129 5 100       23 next unless $line =~ /\S/;
130 4 100       11 last if $line eq 'quit';
131 1         5 $self->run( split /\s+/, $line );
132             }
133 3         8 return;
134             }
135              
136             sub _print
137             {
138 215     215   365 my $self = shift;
139 215         661 return $self->{io}->print( @_ );
140             }
141              
142             sub _prompt
143             {
144 5     5   9 my $self = shift;
145 5         15 return $self->{io}->prompt( @_ );
146             }
147              
148             sub _initialize_config
149             {
150 6     6   15 my ( $self, $config_file ) = @_;
151 6         51 my $conf = Config::Tiny->read( $config_file );
152 6         40 %{ $self->{config} } = (
  6         35  
153 6         14 ( $conf->{_} ? %{ delete $conf->{_} } : () ), # first extract the top level
154 6         17 %{$conf}, # Keep any multi-levels that are not aliases
155 6 50       1282 %{ $self->{config} }, # Override with supplied parameters
156             );
157 6         39 return;
158             }
159              
160             sub _initialize_io_object
161             {
162 37     37   55 my ( $self ) = @_;
163              
164 37         96 my $io = delete $self->{config}->{'io'};
165 37 100       603 if( !defined $io )
    50          
166             {
167             eval {
168 4         30 $io = App::CmdDispatch::IO->new();
169 4 50       6 } or do {
170 0         0 $io = App::CmdDispatch::MinimalIO->new();
171             };
172 4 50       13 die "Unable to create an IO object for CmdDispatch.\n" unless defined $io;
173             }
174             elsif( !_is_valid_io_object( $io ) )
175             {
176 0         0 die "Object supplied as io parameter does not supply correct interface.\n";
177             }
178              
179 37         93 $self->{io} = $io;
180 37         72 return;
181             }
182              
183             sub _is_valid_io_object
184             {
185 33     33   61 my ( $io ) = @_;
186 33 50       93 return unless ref $io;
187 33         67 return 2 == grep { $io->can( $_ ) } qw/print prompt/;
  66         331  
188             }
189              
190             sub _setup_commands
191             {
192 40     40   68 my ( $self, $commands ) = @_;
193 40         59 $commands = { %{$commands} };
  40         121  
194              
195 40 100       191 return $commands unless $self->{config}->{default_commands};
196              
197 23         96 foreach my $def ( split / /, $self->{config}->{default_commands} )
198             {
199 44 100       123 if( $def eq 'shell' )
    50          
200             {
201 22         171 $commands->{shell} = {
202             code => \&App::CmdDispatch::shell,
203             clue => 'shell',
204             abstract => 'Launch an interactive command shell.',
205             help => 'Execute commands as entered until quit.',
206             };
207             }
208             elsif( $def eq 'help' )
209             {
210 22         5587 require App::CmdDispatch::Help;
211 22         147 $self->{_helper} = App::CmdDispatch::Help->new( $self, $commands, $self->{config} );
212             }
213             else
214             {
215 0         0 die "Unrecognized default command: '$def'\n";
216             }
217             }
218 23         64 return $commands;
219             }
220              
221             1;
222              
223             __END__