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