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   314976 use warnings;
  11         25  
  11         291  
4 11     11   50 use strict;
  11         19  
  11         204  
5              
6 11     11   7698 use Config::Tiny;
  11         9710  
  11         293  
7 11     11   7866 use Term::ReadLine;
  11         31879  
  11         290  
8 11     11   5465 use App::CmdDispatch::IO;
  11         27  
  11         383  
9 11     11   5667 use App::CmdDispatch::Table;
  11         25  
  11         14575  
10              
11             our $VERSION = '0.43';
12              
13             sub new
14             {
15 45     45 1 19975 my ( $class, $commands, $options ) = @_;
16              
17 45   100     238 $options ||= {};
18 45 100       322 die "Command definition is not a hashref.\n" unless ref $commands eq ref {};
19 43 100       77 die "No commands specified.\n" unless keys %{$commands};
  43         177  
20 42 100 66     264 die "Options parameter is not a hashref.\n" unless $options and ref $options eq ref {};
21              
22 41         63 $options = { %{$options} };
  41         130  
23 41         114 my $config_file = delete $options->{config};
24 41         52 my $aliases;
25 41         119 my $self = bless { config => $options }, $class;
26             $self->{_command_sorter} = delete $self->{config}->{command_sort}
27 41 50 33     185 if $self->{config}->{command_sort} && 'CODE' eq ref $self->{config}->{command_sort};
28 41 100       142 if( defined $config_file )
29             {
30 7 100       127 die "Supplied config is not a file.\n" unless -f $config_file;
31 6         14 $self->_initialize_config( $config_file );
32             }
33 40         71 $aliases = delete $self->{config}->{alias};
34 40 100       143 $aliases = {} unless ref $aliases eq ref {};
35              
36 40         123 $commands = $self->_setup_commands( $commands );
37              
38             # TODO - replace the hard-coded Table module name with a parameter.
39 40         273 my $table = App::CmdDispatch::Table->new( $commands, $aliases );
40 37 100       123 if( $self->{_helper} )
41             {
42 22         80 $self->{_helper}->normalize_command_help( $table );
43 22   33     235 $self->{_command_sorter} ||= ( ref $self->{_helper} )->can( "sort_commands" );
44             }
45 37         68 $self->{table} = $table;
46 37         92 $self->_initialize_io_object();
47 37   100 13   174 $self->{_command_sorter} ||= sub { return ( sort @_ ); };
  13         73  
48              
49 37         198 return $self;
50             }
51              
52 1     1 1 8 sub get_config { return $_[0]->{config}; }
53              
54             sub run
55             {
56 49     49 1 875 my ( $self, $cmd, @args ) = @_;
57              
58             eval {
59 49         189 $self->{table}->run( $self, $cmd, @args );
60 46         126 1;
61             } or do
62 49 100       66 {
63 3         5 my $ex = $@;
64 3 50       13 if( ref( $ex ) =~ /\AApp::CmdDispatch::Exception/ )
65             {
66 3         10 $self->_print( $ex->why(), "\n" );
67 3         22 $self->command_hint;
68             }
69             else
70             {
71 0         0 die $ex;
72             }
73             };
74 49         141 return;
75             }
76              
77             sub command_list
78             {
79 44     44 1 2758 my ( $self ) = @_;
80 44         141 return $self->{_command_sorter}->( $self->{table}->command_list() );
81             }
82              
83             sub command_hint
84             {
85 3     3 1 5 my ( $self ) = @_;
86 3 50       13 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 6889 my ( $self, $arg ) = @_;
94             eval {
95 19         60 $self->run( 'hint', $arg );
96 19         46 1;
97             } or do
98 19 50       34 {
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         41 return;
103             }
104              
105             sub help
106             {
107 19     19 1 4816 my ( $self, $arg ) = @_;
108             eval {
109 19         39 $self->run( 'help', $arg );
110 19         48 1;
111             } or do
112 19 50       23 {
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         32 return;
117             }
118              
119 23     23 1 626 sub alias_list { return $_[0]->{table}->alias_list(); }
120              
121             sub shell
122             {
123 3     3 1 16 my ( $self ) = @_;
124              
125 3         7 $self->_print( "Enter a command or 'quit' to exit:\n" );
126 3         24 while ( my $line = $self->_prompt( '> ' ) )
127             {
128 5         50 chomp $line;
129 5 100       20 next unless $line =~ /\S/;
130 4 100       10 last if $line eq 'quit';
131 1         6 $self->run( split /\s+/, $line );
132             }
133 3         5 return;
134             }
135              
136             sub _print
137             {
138 215     215   251 my $self = shift;
139 215         558 return $self->{io}->print( @_ );
140             }
141              
142             sub _prompt
143             {
144 5     5   6 my $self = shift;
145 5         15 return $self->{io}->prompt( @_ );
146             }
147              
148             sub _initialize_config
149             {
150 6     6   8 my ( $self, $config_file ) = @_;
151 6         34 my $conf = Config::Tiny->read( $config_file );
152 6         28 %{ $self->{config} } = (
153 6         16 ( $conf->{_} ? %{ delete $conf->{_} } : () ), # first extract the top level
154 6         12 %{$conf}, # Keep any multi-levels that are not aliases
155 6 50       792 %{ $self->{config} }, # Override with supplied parameters
  6         13  
156             );
157 6         29 return;
158             }
159              
160             sub _initialize_io_object
161             {
162 37     37   62 my ( $self ) = @_;
163              
164 37         76 my $io = delete $self->{config}->{'io'};
165 37 100       118 if( !defined $io )
    50          
166             {
167             eval {
168 4         26 $io = App::CmdDispatch::IO->new();
169 4 50       8 } or do {
170 0         0 $io = App::CmdDispatch::MinimalIO->new();
171             };
172 4 50       27 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         79 $self->{io} = $io;
180 37         68 return;
181             }
182              
183             sub _is_valid_io_object
184             {
185 33     33   42 my ( $io ) = @_;
186 33 50       84 return unless ref $io;
187 33         58 return 2 == grep { $io->can( $_ ) } qw/print prompt/;
  66         273  
188             }
189              
190             sub _setup_commands
191             {
192 40     40   61 my ( $self, $commands ) = @_;
193 40         111 $commands = { %{$commands} };
  40         99  
194              
195 40 100       136 return $commands unless $self->{config}->{default_commands};
196              
197 23         85 foreach my $def ( split / /, $self->{config}->{default_commands} )
198             {
199 44 100       135 if( $def eq 'shell' )
    50          
200             {
201             $commands->{shell} = {
202 22         114 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         5355 require App::CmdDispatch::Help;
211 22         120 $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         61 return $commands;
219             }
220              
221             1;
222              
223             __END__