File Coverage

blib/lib/Net/CLI/Interact/Role/Engine.pm
Criterion Covered Total %
statement 24 79 30.3
branch 0 26 0.0
condition 0 15 0.0
subroutine 8 15 53.3
pod 3 4 75.0
total 35 139 25.1


line stmt bran cond sub pod time code
1             package Net::CLI::Interact::Role::Engine;
2             { $Net::CLI::Interact::Role::Engine::VERSION = '2.400000' }
3              
4             {
5             package # hide from pause
6             Net::CLI::Interact::Role::Engine::ExecuteOptions;
7              
8 1     1   626 use Moo;
  1         3  
  1         6  
9 1     1   363 use Sub::Quote;
  1         2  
  1         84  
10 1     1   7 use MooX::Types::MooseLike::Base qw(Bool ArrayRef Str Any);
  1         2  
  1         205  
11              
12             has 'no_ors' => (
13             is => 'ro',
14             isa => Bool,
15             default => quote_sub('0'),
16             );
17              
18             has 'params' => (
19             is => 'ro',
20             isa => ArrayRef[Str],
21             predicate => 1,
22             );
23              
24             has 'timeout' => (
25             is => 'ro',
26             isa => quote_sub(q{die "$_[0] is not a posint!" unless $_[0] > 0 }),
27             );
28              
29             has 'match' => (
30             is => 'rw',
31             isa => ArrayRef, # FIXME ArrayRef[RegexpRef|Str]
32             predicate => 1,
33             coerce => quote_sub(q{ (ref [] ne ref $_[0]) ? [$_[0]] : $_[0] }),
34             );
35             }
36              
37             # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38              
39 1     1   11 use Moo::Role;
  1         2  
  1         15  
40 1     1   374 use MooX::Types::MooseLike::Base qw(InstanceOf);
  1         2  
  1         54  
41              
42             with 'Net::CLI::Interact::Role::Prompt';
43              
44 1     1   5 use Net::CLI::Interact::Action;
  1         24  
  1         24  
45 1     1   6 use Net::CLI::Interact::ActionSet;
  1         2  
  1         33  
46 1     1   8 use Class::Load ();
  1         2  
  1         1243  
47              
48             # try to load Data::Printer for last_actionset debug output
49             if (Class::Load::try_load_class('Data::Printer', {-version => '0.27'})) {
50             Data::Printer->import({class => { expand => 'all' }});
51             }
52              
53             has 'last_actionset' => (
54             is => 'rw',
55             isa => InstanceOf['Net::CLI::Interact::ActionSet'],
56             trigger => 1,
57             );
58              
59             sub _trigger_last_actionset {
60 0     0     my ($self, $new) = @_;
61 0           $self->logger->log('prompt', 'notice',
62             sprintf ('output matching prompt was "%s"', $new->item_at(-1)->response));
63 0 0         if (Class::Load::is_class_loaded('Data::Printer', {-version => '0.27'})) {
64 0           Data::Printer::p($new, output => \my $debug);
65 0           $self->logger->log('object', 'debug', $debug);
66             }
67             }
68              
69             sub last_response {
70 0     0 1   my $self = shift;
71 0           my $irs_re = $self->transport->irs_re;
72 0           (my $resp = $self->last_actionset->item_at(-2)->response) =~ s/$irs_re/\n/g;
73 0           $resp =~ s/\n+$//;
74             return (wantarray
75 0 0         ? (map {$_ .= "\n"} split m/\n/, $resp)
  0            
76             : ($resp ."\n"));
77             }
78              
79             has 'default_continuation' => (
80             is => 'rw',
81             isa => InstanceOf['Net::CLI::Interact::ActionSet'],
82             writer => '_default_continuation',
83             predicate => 1,
84             clearer => 1,
85             );
86              
87             sub set_default_continuation {
88 0     0 0   my ($self, $cont) = @_;
89 0 0         die "missing continuation" unless $cont;
90             die "unknown continuation [$cont]" unless
91 0 0         eval{ $self->phrasebook->macro($cont) };
  0            
92 0           $self->_default_continuation( $self->phrasebook->macro($cont) );
93 0           $self->logger->log('engine', 'info', 'default continuation set to', $cont);
94             }
95              
96             sub cmd {
97 0     0 1   my ($self, $command, $options) = @_;
98 0   0       $options = Net::CLI::Interact::Role::Engine::ExecuteOptions->new($options || {});
99              
100 0           $self->logger->log('engine', 'notice', 'running command', $command);
101              
102 0 0         if ($options->has_match) {
103             # convert prompt name(s) from name into regexpref, or die
104             $options->match([
105 0 0         map { ref $_ eq ref '' ? @{ $self->phrasebook->prompt($_)->first->value }
  0            
106             : $_ }
107 0           @{ $options->match }
  0            
108             ]);
109              
110             $self->logger->log('engine', 'info', 'to match',
111 0 0         (ref $options->match eq ref [] ? (join '|', @{$options->match})
  0            
112             : $options->match));
113             }
114              
115             # command will be run through sprintf but without any params
116             # so convert any embedded % to literal %
117 0 0         ($command =~ s/%/%%/g) &&
118             $self->logger->log('engine', 'debug', 'command expanded to:', $command);
119              
120 0           return $self->_execute_actions(
121             $options,
122             Net::CLI::Interact::Action->new({
123             type => 'send',
124             value => $command,
125             no_ors => $options->no_ors,
126             }),
127             );
128             }
129              
130             sub macro {
131 0     0 1   my ($self, $name, $options) = @_;
132 0   0       $options = Net::CLI::Interact::Role::Engine::ExecuteOptions->new($options || {});
133              
134 0           $self->logger->log('engine', 'notice', 'running macro', $name);
135             $self->logger->log('engine', 'info', 'macro params are:',
136 0 0         join ', ', @{ $options->params }) if $options->has_params;
  0            
137              
138 0           my $set = $self->phrasebook->macro($name)->clone;
139 0 0         $set->apply_params(@{ $options->params }) if $options->has_params;
  0            
140              
141 0           return $self->_execute_actions($options, $set);
142             }
143              
144             sub _execute_actions {
145 0     0     my ($self, $options, @actions) = @_;
146              
147 0           $self->logger->log('engine', 'notice', 'executing actions');
148              
149             # make connection on transport if not yet done
150 0 0         $self->transport->init if not $self->transport->connect_ready;
151              
152             # user can install a prompt, call find_prompt, or let us trigger that
153 0 0 0       $self->find_prompt(1) if not ($self->prompt_re || $self->last_actionset);
154              
155 0 0 0       my $set = Net::CLI::Interact::ActionSet->new({
156             actions => [@actions],
157             current_match => ($options->match || $self->prompt_re || $self->last_prompt_re),
158             ($self->has_default_continuation ? (default_continuation => $self->default_continuation) : ()),
159             });
160 0     0     $set->register_callback(sub { $self->transport->do_action(@_) });
  0            
161              
162 0           $self->logger->log('engine', 'debug', 'dispatching to execute method');
163 0           my $timeout_bak = $self->transport->timeout;
164              
165 0   0       $self->transport->timeout($options->timeout || $timeout_bak);
166 0           $set->execute;
167 0           $self->transport->timeout($timeout_bak);
168 0           $self->last_actionset($set);
169              
170 0   0       $self->logger->log('prompt', 'debug',
171             sprintf 'setting new prompt to %s',
172             $self->last_actionset->last->prompt_hit || '<none>');
173 0           $self->_prompt( $self->last_actionset->last->prompt_hit );
174              
175 0           $self->logger->log('dialogue', 'info',
176             "trimmed command response:\n". $self->last_response);
177 0           return $self->last_response; # context sensitive
178             }
179              
180             1;
181              
182             =pod
183              
184             =head1 NAME
185              
186             Net::CLI::Interact::Role::Engine - Statement execution engine
187              
188             =head1 DESCRIPTION
189              
190             This module is the core of L<Net::CLI::Interact>, and serves to take entries
191             from your loaded L<Phrasebooks|Net::CLI::Interact::Phrasebook>, issue them to
192             connected devices, and gather the returned output.
193              
194             =head1 INTERFACE
195              
196             =head2 cmd( $command_statement, \%options? )
197              
198             Execute a single command statement on the connected device, and consume output
199             until there is a match with the current I<prompt>. The statement is executed
200             verbatim on the device, with a newline appended.
201              
202             The following options are supported:
203              
204             =over 4
205              
206             =item C<< timeout => $seconds >> (optional)
207              
208             Sets a value of C<timeout> for the
209             L<Transport|Net::CLI::Interact::Transport> local to this call of C<cmd>, that
210             overrides whatever is set in the Transport, or the default of 10 seconds.
211              
212             =item C<< no_ors => 1 >> (optional)
213              
214             When passed a true value, a newline character (in fact the value of C<ors>)
215             will not be appended to the statement sent to the device.
216              
217             =item C<< match => $name | $regexpref | \@names_and_regexprefs >> (optional)
218              
219             Allows this command (only) to complete with a custom match, which must be one
220             or more of either the name of a loaded phrasebook Prompt or your own regular
221             expression reference (C<< qr// >>). The module updates the current prompt to
222             be the same value on a successful match.
223              
224             =back
225              
226             In scalar context the C<last_response> is returned (see below). In list
227             context the gathered response is returned as a list of lines. In both cases
228             your local platform's newline character will end all lines.
229              
230             =head2 macro( $macro_name, \%options? )
231              
232             Execute the commands contained within the named Macro, which must be loaded
233             from a Phrasebook. Options to control the output, including variables for
234             substitution into the Macro, are passed in the C<%options> hash reference.
235              
236             The following options are supported:
237              
238             =over 4
239              
240             =item C<< params => \@values >> (optional)
241              
242             If the Macro contains commands using C<sprintf> Format variables then the
243             corresponding parameters must be passed in this value as an array reference.
244              
245             Values are consumed from the provided array reference and passed to the
246             C<send> commands in the Macro in order, as needed. An exception will be thrown
247             if there are insufficient parameters.
248              
249             =item C<< timeout => $seconds >> (optional)
250              
251             Sets a value of C<timeout> for the
252             L<Transport|Net::CLI::Interact::Transport> local to this call of C<macro>,
253             that overrides whatever is set in the Transport, or the default of 10 seconds.
254              
255             =back
256              
257             An exception will be thrown if the Match statements in the Macro are not
258             successful against the output returned from the device. This is based on the
259             value of C<timeout>, which controls how long the module waits for matching
260             output.
261              
262             In scalar context the C<last_response> is returned (see below). In list
263             context the gathered response is returned as a list of lines. In both cases
264             your local platform's newline character will end all lines.
265              
266             =head2 last_response
267              
268             Returns the gathered output after issuing the last recent C<send> command
269             within the most recent C<cmd> or C<prompt>. That is, you get the output from
270             the last command sent to the connected device.
271              
272             In scalar context all data is returned. In list context the gathered response
273             is returned as a list of lines. In both cases your local platform's newline
274             character will end all lines.
275              
276             =head2 last_actionset
277              
278             Returns the complete L<ActionSet|Net::CLI::Interact::ActionSet> that was
279             constructed from the most recent C<macro> or C<cmd> execution. This will be a
280             sequence of L<Actions|Net::CLI::Interact::Action> that correspond to C<send>
281             and C<match> statements.
282              
283             In the case of a Macro these directly relate to the contents of your
284             Phrasebook, with the possible addition of C<match> statements added
285             automatically. In the case of a C<cmd> execution, an "anonymous" Macro is
286             constructed which consists of a single C<send> and a single C<match>.
287              
288             =head1 COMPOSITION
289              
290             See the following for further interface details:
291              
292             =over 4
293              
294             =item *
295              
296             L<Net::CLI::Interact::Role::Prompt>
297              
298             =back
299              
300             =cut
301