File Coverage

blib/lib/Net/CLI/Interact/Role/Engine.pm
Criterion Covered Total %
statement 24 78 30.7
branch 0 26 0.0
condition 0 15 0.0
subroutine 8 15 53.3
pod 3 4 75.0
total 35 138 25.3


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